package com.novoda.noplayer.internal.mediaplayer; import android.content.Context; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; import android.support.annotation.Nullable; import android.view.Surface; import android.view.SurfaceHolder; import com.novoda.noplayer.internal.mediaplayer.PlaybackStateChecker.PlaybackState; import com.novoda.noplayer.internal.mediaplayer.forwarder.MediaPlayerForwarder; import com.novoda.noplayer.internal.utils.NoPlayerLog; import com.novoda.noplayer.internal.utils.Optional; import com.novoda.noplayer.model.AudioTracks; import com.novoda.noplayer.model.Either; import com.novoda.noplayer.model.PlayerAudioTrack; import com.novoda.noplayer.model.PlayerSubtitleTrack; import com.novoda.noplayer.model.PlayerVideoTrack; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; import static com.novoda.noplayer.internal.mediaplayer.PlaybackStateChecker.PlaybackState.IDLE; import static com.novoda.noplayer.internal.mediaplayer.PlaybackStateChecker.PlaybackState.PAUSED; import static com.novoda.noplayer.internal.mediaplayer.PlaybackStateChecker.PlaybackState.PLAYING; // Not much we can do, wrapping MediaPlayer is a lot of work @SuppressWarnings("PMD.GodClass") class AndroidMediaPlayerFacade { private static final Map<String, String> NO_HEADERS = null; private final Context context; private final MediaPlayerForwarder forwarder; private final AudioManager audioManager; private final AndroidMediaPlayerAudioTrackSelector trackSelector; private final PlaybackStateChecker playbackStateChecker; private final MediaPlayerCreator mediaPlayerCreator; private PlaybackState currentState = IDLE; private int currentBufferPercentage; private float volume = 1.0f; @Nullable private MediaPlayer mediaPlayer; static AndroidMediaPlayerFacade newInstance(Context context, MediaPlayerForwarder forwarder) { TrackInfosFactory trackInfosFactory = new TrackInfosFactory(); AndroidMediaPlayerAudioTrackSelector trackSelector = new AndroidMediaPlayerAudioTrackSelector(trackInfosFactory); PlaybackStateChecker playbackStateChecker = new PlaybackStateChecker(); AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); MediaPlayerCreator mediaPlayerCreator = new MediaPlayerCreator(); return new AndroidMediaPlayerFacade(context, forwarder, audioManager, trackSelector, playbackStateChecker, mediaPlayerCreator); } AndroidMediaPlayerFacade(Context context, MediaPlayerForwarder forwarder, AudioManager audioManager, AndroidMediaPlayerAudioTrackSelector trackSelector, PlaybackStateChecker playbackStateChecker, MediaPlayerCreator mediaPlayerCreator) { this.context = context; this.forwarder = forwarder; this.audioManager = audioManager; this.trackSelector = trackSelector; this.playbackStateChecker = playbackStateChecker; this.mediaPlayerCreator = mediaPlayerCreator; } void prepareVideo(Uri videoUri, Either<Surface, SurfaceHolder> surface) { requestAudioFocus(); release(); try { currentState = PlaybackState.PREPARING; mediaPlayer = createAndBindMediaPlayer(surface, videoUri); mediaPlayer.prepareAsync(); } catch (IOException | IllegalArgumentException | IllegalStateException ex) { reportCreationError(ex, videoUri); } } private void requestAudioFocus() { audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); } private MediaPlayer createAndBindMediaPlayer(Either<Surface, SurfaceHolder> surface, Uri videoUri) throws IOException, IllegalStateException, IllegalArgumentException { MediaPlayer mediaPlayer = mediaPlayerCreator.createMediaPlayer(); mediaPlayer.setOnPreparedListener(internalPreparedListener); mediaPlayer.setOnVideoSizeChangedListener(internalSizeChangedListener); mediaPlayer.setOnCompletionListener(internalCompletionListener); mediaPlayer.setOnErrorListener(internalErrorListener); mediaPlayer.setOnBufferingUpdateListener(internalBufferingUpdateListener); mediaPlayer.setDataSource(context, videoUri, NO_HEADERS); attachSurface(mediaPlayer, surface); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setScreenOnWhilePlaying(true); currentBufferPercentage = 0; volume = 1.0f; return mediaPlayer; } private void reportCreationError(Exception ex, Uri videoUri) { NoPlayerLog.w(ex, "Unable to open content: " + videoUri); currentState = PlaybackState.ERROR; internalErrorListener.onError(mediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); } private final MediaPlayer.OnVideoSizeChangedListener internalSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() { @Override public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { MediaPlayer.OnVideoSizeChangedListener onVideoSizeChangedForwarder = forwarder.onSizeChangedListener(); if (onVideoSizeChangedForwarder == null) { throw new IllegalStateException("Should bind a OnVideoSizeChangedListener. Cannot forward events."); } onVideoSizeChangedForwarder.onVideoSizeChanged(mp, width, height); } }; private final MediaPlayer.OnPreparedListener internalPreparedListener = new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { currentState = PlaybackState.PREPARED; MediaPlayer.OnPreparedListener onPreparedForwarder = forwarder.onPreparedListener(); if (onPreparedForwarder == null) { throw new IllegalStateException("Should bind a OnPreparedListener. Cannot forward events."); } onPreparedForwarder.onPrepared(mediaPlayer); } }; private final MediaPlayer.OnCompletionListener internalCompletionListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { currentState = PlaybackState.COMPLETED; MediaPlayer.OnCompletionListener onCompletionForwarder = forwarder.onCompletionListener(); if (onCompletionForwarder == null) { throw new IllegalStateException("Should bind a OnCompletionListener. Cannot forward events."); } onCompletionForwarder.onCompletion(mediaPlayer); } }; private final MediaPlayer.OnErrorListener internalErrorListener = new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { NoPlayerLog.d("Error: " + what + "," + extra); currentState = PlaybackState.ERROR; MediaPlayer.OnErrorListener onErrorForwarder = forwarder.onErrorListener(); if (onErrorForwarder == null) { throw new IllegalStateException("Should bind a OnErrorListener. Cannot forward events."); } return onErrorForwarder.onError(mediaPlayer, what, extra); } }; private final MediaPlayer.OnBufferingUpdateListener internalBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { @Override public void onBufferingUpdate(MediaPlayer mp, int percent) { currentBufferPercentage = percent; } }; void release() { if (hasPlayer()) { mediaPlayer.reset(); mediaPlayer.release(); mediaPlayer = null; currentState = IDLE; } } void start(Either<Surface, SurfaceHolder> surface) throws IllegalStateException { assertIsInPlaybackState(); attachSurface(mediaPlayer, surface); currentState = PLAYING; mediaPlayer.start(); } private void attachSurface(final MediaPlayer mediaPlayer, Either<Surface, SurfaceHolder> surface) { Either.Consumer<Surface> setSurface = new Either.Consumer<Surface>() { @Override public void accept(Surface value) { mediaPlayer.setSurface(value); } }; Either.Consumer<SurfaceHolder> setDisplay = new Either.Consumer<SurfaceHolder>() { @Override public void accept(SurfaceHolder value) { mediaPlayer.setDisplay(value); } }; surface.apply(setSurface, setDisplay); } void pause() throws IllegalStateException { assertIsInPlaybackState(); if (isPlaying()) { mediaPlayer.pause(); currentState = PAUSED; } } int mediaDurationInMillis() throws IllegalStateException { assertIsInPlaybackState(); return mediaPlayer.getDuration(); } int currentPositionInMillis() throws IllegalStateException { assertIsInPlaybackState(); return mediaPlayer.getCurrentPosition(); } void seekTo(long positionInMillis) throws IllegalStateException { assertIsInPlaybackState(); mediaPlayer.seekTo((int) positionInMillis); } boolean isPlaying() { return playbackStateChecker.isPlaying(mediaPlayer, currentState); } int getBufferPercentage() throws IllegalStateException { assertIsInPlaybackState(); return currentBufferPercentage; } AudioTracks getAudioTracks() throws IllegalStateException { assertIsInPlaybackState(); return trackSelector.getAudioTracks(mediaPlayer); } boolean selectAudioTrack(PlayerAudioTrack playerAudioTrack) throws IllegalStateException { assertIsInPlaybackState(); return trackSelector.selectAudioTrack(mediaPlayer, playerAudioTrack); } boolean clearAudioTrackSelection() { assertIsInPlaybackState(); NoPlayerLog.w("Tried to clear audio track selection but has not been implemented for MediaPlayer."); return false; } void setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener seekToResettingSeekListener) throws IllegalStateException { assertIsInPlaybackState(); mediaPlayer.setOnSeekCompleteListener(seekToResettingSeekListener); } boolean hasPlayedContent() { return hasPlayer(); } private boolean hasPlayer() { return mediaPlayer != null; } boolean clearSubtitleTrack() throws IllegalStateException { assertIsInPlaybackState(); NoPlayerLog.w("Tried to hide subtitle track but has not been implemented for MediaPlayer."); return false; } boolean selectSubtitleTrack(PlayerSubtitleTrack subtitleTrack) throws IllegalStateException { assertIsInPlaybackState(); NoPlayerLog.w("Tried to select subtitle track but has not been implemented for MediaPlayer."); return false; } List<PlayerSubtitleTrack> getSubtitleTracks() throws IllegalStateException { assertIsInPlaybackState(); NoPlayerLog.w("Tried to get subtitle tracks but has not been implemented for MediaPlayer."); return Collections.emptyList(); } private void assertIsInPlaybackState() throws IllegalStateException { if (!playbackStateChecker.isInPlaybackState(mediaPlayer, currentState)) { throw new IllegalStateException("Video must be loaded and not in an error state before trying to interact with the player"); } } Optional<PlayerVideoTrack> getSelectedVideoTrack() { assertIsInPlaybackState(); NoPlayerLog.w("Tried to get the currently playing video track but has not been implemented for MediaPlayer."); return Optional.absent(); } List<PlayerVideoTrack> getVideoTracks() { assertIsInPlaybackState(); NoPlayerLog.w("Tried to get video tracks but has not been implemented for MediaPlayer."); return Collections.emptyList(); } boolean selectVideoTrack(PlayerVideoTrack videoTrack) { assertIsInPlaybackState(); NoPlayerLog.w("Tried to select a video track but has not been implemented for MediaPlayer."); return false; } boolean clearVideoTrackSelection() { assertIsInPlaybackState(); NoPlayerLog.w("Tried to clear video track selection but has not been implemented for MediaPlayer."); return false; } void setRepeating(boolean repeating) { assertIsInPlaybackState(); mediaPlayer.setLooping(repeating); } void setVolume(float volume) { assertIsInPlaybackState(); this.volume = volume; mediaPlayer.setVolume(volume, volume); } float getVolume() { assertIsInPlaybackState(); return volume; } void clearMaxVideoBitrate() { assertIsInPlaybackState(); NoPlayerLog.w("Tried to clear max video bitrate but has not been implemented for MediaPlayer."); } void setMaxVideoBitrate(int maxVideoBitrate) { assertIsInPlaybackState(); NoPlayerLog.w("Tried to set max video bitrate but has not been implemented for MediaPlayer."); } }