| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.tv.dvr; |
| |
| import android.app.Activity; |
| import android.content.Intent; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.media.MediaMetadata; |
| import android.media.session.MediaController; |
| import android.media.session.MediaSession; |
| import android.media.session.PlaybackState; |
| import android.media.tv.TvContract; |
| import android.os.AsyncTask; |
| import android.support.annotation.Nullable; |
| import android.text.TextUtils; |
| |
| import com.android.tv.R; |
| import com.android.tv.TvApplication; |
| import com.android.tv.data.Channel; |
| import com.android.tv.data.ChannelDataManager; |
| import com.android.tv.dvr.ui.DvrPlaybackOverlayFragment; |
| import com.android.tv.util.ImageLoader; |
| import com.android.tv.util.TimeShiftUtils; |
| import com.android.tv.util.Utils; |
| |
| public class DvrPlaybackMediaSessionHelper { |
| private static final String TAG = "DvrPlaybackMediaSessionHelper"; |
| private static final boolean DEBUG = false; |
| |
| private int mNowPlayingCardWidth; |
| private int mNowPlayingCardHeight; |
| private int mSpeedLevel; |
| private long mProgramDurationMs; |
| |
| private Activity mActivity; |
| private DvrPlayer mDvrPlayer; |
| private MediaSession mMediaSession; |
| private final DvrWatchedPositionManager mDvrWatchedPositionManager; |
| private final ChannelDataManager mChannelDataManager; |
| |
| public DvrPlaybackMediaSessionHelper(Activity activity, String mediaSessionTag, |
| DvrPlayer dvrPlayer, DvrPlaybackOverlayFragment overlayFragment) { |
| mActivity = activity; |
| mDvrPlayer = dvrPlayer; |
| mDvrWatchedPositionManager = |
| TvApplication.getSingletons(activity).getDvrWatchedPositionManager(); |
| mChannelDataManager = TvApplication.getSingletons(activity).getChannelDataManager(); |
| mDvrPlayer.setCallback(new DvrPlayer.DvrPlayerCallback() { |
| @Override |
| public void onPlaybackStateChanged(int playbackState, int playbackSpeed) { |
| updateMediaSessionPlaybackState(); |
| } |
| |
| @Override |
| public void onPlaybackPositionChanged(long positionMs) { |
| updateMediaSessionPlaybackState(); |
| if (mDvrPlayer.isPlaybackPrepared()) { |
| mDvrWatchedPositionManager |
| .setWatchedPosition(mDvrPlayer.getProgram().getId(), positionMs); |
| } |
| } |
| |
| @Override |
| public void onPlaybackEnded() { |
| // TODO: Deal with watched over recordings in DVR library |
| RecordedProgram nextEpisode = |
| overlayFragment.getNextEpisode(mDvrPlayer.getProgram()); |
| if (nextEpisode == null) { |
| mDvrPlayer.reset(); |
| mActivity.finish(); |
| } else { |
| Intent intent = new Intent(activity, DvrPlaybackActivity.class); |
| intent.putExtra(Utils.EXTRA_KEY_RECORDED_PROGRAM_ID, nextEpisode.getId()); |
| mActivity.startActivity(intent); |
| } |
| } |
| }); |
| initializeMediaSession(mediaSessionTag); |
| } |
| |
| /** |
| * Stops DVR player and release media session. |
| */ |
| public void release() { |
| if (mDvrPlayer != null) { |
| mDvrPlayer.reset(); |
| } |
| if (mMediaSession != null) { |
| mMediaSession.release(); |
| } |
| } |
| |
| /** |
| * Updates media session's playback state and speed. |
| */ |
| public void updateMediaSessionPlaybackState() { |
| mMediaSession.setPlaybackState(new PlaybackState.Builder() |
| .setState(mDvrPlayer.getPlaybackState(), mDvrPlayer.getPlaybackPosition(), |
| mSpeedLevel).build()); |
| } |
| |
| /** |
| * Sets the recorded program for playback. |
| * |
| * @param program The recorded program to play. {@code null} to reset the DVR player. |
| */ |
| public void setupPlayback(RecordedProgram program, long seekPositionMs) { |
| if (program != null) { |
| mDvrPlayer.setProgram(program, seekPositionMs); |
| setupMediaSession(program); |
| } else { |
| mDvrPlayer.reset(); |
| mMediaSession.setActive(false); |
| } |
| } |
| |
| /** |
| * Returns the recorded program now playing. |
| */ |
| public RecordedProgram getProgram() { |
| return mDvrPlayer.getProgram(); |
| } |
| |
| /** |
| * Checks if the recorded program is the same as now playing one. |
| */ |
| public boolean isCurrentProgram(RecordedProgram program) { |
| return program != null && program.equals(getProgram()); |
| } |
| |
| /** |
| * Returns playback state. |
| */ |
| public int getPlaybackState() { |
| return mDvrPlayer.getPlaybackState(); |
| } |
| |
| /** |
| * Returns the underlying DVR player. |
| */ |
| public DvrPlayer getDvrPlayer() { |
| return mDvrPlayer; |
| } |
| |
| private void initializeMediaSession(String mediaSessionTag) { |
| mMediaSession = new MediaSession(mActivity, mediaSessionTag); |
| mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
| | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); |
| mNowPlayingCardWidth = mActivity.getResources() |
| .getDimensionPixelSize(R.dimen.notif_card_img_max_width); |
| mNowPlayingCardHeight = mActivity.getResources() |
| .getDimensionPixelSize(R.dimen.notif_card_img_height); |
| mMediaSession.setCallback(new MediaSessionCallback()); |
| mActivity.setMediaController( |
| new MediaController(mActivity, mMediaSession.getSessionToken())); |
| updateMediaSessionPlaybackState(); |
| } |
| |
| private void setupMediaSession(RecordedProgram program) { |
| mProgramDurationMs = program.getDurationMillis(); |
| String cardTitleText = program.getTitle(); |
| if (TextUtils.isEmpty(cardTitleText)) { |
| Channel channel = mChannelDataManager.getChannel(program.getChannelId()); |
| cardTitleText = (channel != null) ? channel.getDisplayName() |
| : mActivity.getString(R.string.no_program_information); |
| } |
| updateMediaMetadata(program.getId(), cardTitleText, program.getDescription(), |
| mProgramDurationMs, null, 0); |
| String posterArtUri = program.getPosterArtUri(); |
| if (posterArtUri == null) { |
| posterArtUri = TvContract.buildChannelLogoUri(program.getChannelId()).toString(); |
| } |
| updatePosterArt(program, cardTitleText, program.getDescription(), |
| mProgramDurationMs, null, posterArtUri); |
| mMediaSession.setActive(true); |
| } |
| |
| private void updatePosterArt(RecordedProgram program, String cardTitleText, |
| String cardSubtitleText, long duration, |
| @Nullable Bitmap posterArt, @Nullable String posterArtUri) { |
| if (posterArt != null) { |
| updateMediaMetadata(program.getId(), cardTitleText, |
| cardSubtitleText, duration, posterArt, 0); |
| } else if (posterArtUri != null) { |
| ImageLoader.loadBitmap(mActivity, posterArtUri, mNowPlayingCardWidth, |
| mNowPlayingCardHeight, new ProgramPosterArtCallback( |
| mActivity, program, cardTitleText, cardSubtitleText, duration)); |
| } else { |
| updateMediaMetadata(program.getId(), cardTitleText, |
| cardSubtitleText, duration, null, R.drawable.default_now_card); |
| } |
| } |
| |
| private class ProgramPosterArtCallback extends |
| ImageLoader.ImageLoaderCallback<Activity> { |
| private RecordedProgram mRecordedProgram; |
| private String mCardTitleText; |
| private String mCardSubtitleText; |
| private long mDuration; |
| |
| public ProgramPosterArtCallback(Activity activity, RecordedProgram program, |
| String cardTitleText, String cardSubtitleText, long duration) { |
| super(activity); |
| mRecordedProgram = program; |
| mCardTitleText = cardTitleText; |
| mCardSubtitleText = cardSubtitleText; |
| mDuration = duration; |
| } |
| |
| @Override |
| public void onBitmapLoaded(Activity activity, @Nullable Bitmap posterArt) { |
| if (isCurrentProgram(mRecordedProgram)) { |
| updatePosterArt(mRecordedProgram, mCardTitleText, |
| mCardSubtitleText, mDuration, posterArt, null); |
| } |
| } |
| } |
| |
| private void updateMediaMetadata(final long programId, final String title, |
| final String subtitle, final long duration, |
| final Bitmap posterArt, final int imageResId) { |
| new AsyncTask<Void, Void, Void>() { |
| @Override |
| protected Void doInBackground(Void... arg0) { |
| MediaMetadata.Builder builder = new MediaMetadata.Builder(); |
| builder.putLong(MediaMetadata.METADATA_KEY_MEDIA_ID, programId) |
| .putString(MediaMetadata.METADATA_KEY_TITLE, title) |
| .putLong(MediaMetadata.METADATA_KEY_DURATION, duration); |
| if (subtitle != null) { |
| builder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle); |
| } |
| Bitmap programPosterArt = posterArt; |
| if (programPosterArt == null && imageResId != 0) { |
| programPosterArt = |
| BitmapFactory.decodeResource(mActivity.getResources(), imageResId); |
| } |
| if (programPosterArt != null) { |
| builder.putBitmap(MediaMetadata.METADATA_KEY_ART, programPosterArt); |
| } |
| mMediaSession.setMetadata(builder.build()); |
| return null; |
| } |
| }.execute(); |
| } |
| |
| // An event was triggered by MediaController.TransportControls and must be handled here. |
| // Here we update the media itself to act on the event that was triggered. |
| private class MediaSessionCallback extends MediaSession.Callback { |
| @Override |
| public void onPrepare() { |
| if (!mDvrPlayer.isPlaybackPrepared()) { |
| mDvrPlayer.prepare(true); |
| } |
| } |
| |
| @Override |
| public void onPlay() { |
| if (mDvrPlayer.isPlaybackPrepared()) { |
| mDvrPlayer.play(); |
| } |
| } |
| |
| @Override |
| public void onPause() { |
| if (mDvrPlayer.isPlaybackPrepared()) { |
| mDvrPlayer.pause(); |
| } |
| } |
| |
| @Override |
| public void onFastForward() { |
| if (!mDvrPlayer.isPlaybackPrepared()) { |
| return; |
| } |
| if (mDvrPlayer.getPlaybackState() == PlaybackState.STATE_FAST_FORWARDING) { |
| if (mSpeedLevel < TimeShiftUtils.MAX_SPEED_LEVEL) { |
| mSpeedLevel++; |
| } else { |
| return; |
| } |
| } else { |
| mSpeedLevel = 0; |
| } |
| mDvrPlayer.fastForward( |
| TimeShiftUtils.getPlaybackSpeed(mSpeedLevel, mProgramDurationMs)); |
| } |
| |
| @Override |
| public void onRewind() { |
| if (!mDvrPlayer.isPlaybackPrepared()) { |
| return; |
| } |
| if (mDvrPlayer.getPlaybackState() == PlaybackState.STATE_REWINDING) { |
| if (mSpeedLevel < TimeShiftUtils.MAX_SPEED_LEVEL) { |
| mSpeedLevel++; |
| } else { |
| return; |
| } |
| } else { |
| mSpeedLevel = 0; |
| } |
| mDvrPlayer.rewind(TimeShiftUtils.getPlaybackSpeed(mSpeedLevel, mProgramDurationMs)); |
| } |
| |
| @Override |
| public void onSeekTo(long positionMs) { |
| if (mDvrPlayer.isPlaybackPrepared()) { |
| mDvrPlayer.seekTo(positionMs); |
| } |
| } |
| } |
| } |