/*
* Copyright (C) 2016 Brian Wernick
*
* 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.devbrackets.android.exomedia;
import android.content.Context;
import android.media.AudioManager;
import android.net.Uri;
import android.support.annotation.FloatRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.devbrackets.android.exomedia.core.ListenerMux;
import com.devbrackets.android.exomedia.core.api.AudioPlayerApi;
import com.devbrackets.android.exomedia.core.audio.ExoAudioPlayer;
import com.devbrackets.android.exomedia.core.audio.NativeAudioPlayer;
import com.devbrackets.android.exomedia.core.exoplayer.ExoMediaPlayer;
import com.devbrackets.android.exomedia.core.listener.MetadataListener;
import com.devbrackets.android.exomedia.listener.OnBufferUpdateListener;
import com.devbrackets.android.exomedia.listener.OnCompletionListener;
import com.devbrackets.android.exomedia.listener.OnErrorListener;
import com.devbrackets.android.exomedia.listener.OnPreparedListener;
import com.devbrackets.android.exomedia.listener.OnSeekCompletionListener;
import com.devbrackets.android.exomedia.util.DeviceUtil;
import com.google.android.exoplayer2.drm.MediaDrmCallback;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import java.util.Map;
/**
* An AudioPlayer that uses the ExoPlayer as the backing architecture. If the current device
* does <em>NOT</em> pass the Android Compatibility Test Suite (CTS) then the backing architecture
* will fall back to using the default Android MediaPlayer.
* <p>
* To help with quick conversions from the Android MediaPlayer this class follows the APIs
* the Android MediaPlayer provides.
*/
@SuppressWarnings("UnusedDeclaration")
public class AudioPlayer {
protected ListenerMux listenerMux;
protected AudioPlayerApi audioPlayerImpl;
protected long overriddenDuration = -1;
public AudioPlayer(@NonNull Context context) {
this(context, new DeviceUtil());
}
public AudioPlayer(@NonNull Context context, @NonNull DeviceUtil deviceUtil) {
init(deviceUtil.supportsExoPlayer(context) ? new ExoAudioPlayer(context) : new NativeAudioPlayer(context));
}
public AudioPlayer(AudioPlayerApi audioPlayerImpl) {
init(audioPlayerImpl);
}
protected void init(AudioPlayerApi audioPlayerImpl) {
this.audioPlayerImpl = audioPlayerImpl;
listenerMux = new ListenerMux(new MuxNotifier());
audioPlayerImpl.setListenerMux(listenerMux);
}
/**
* Returns the audio session ID.
*
* @return the audio session ID.
* Note that the audio session ID is 0 only if a problem occurred when the AudioPlayer was constructed or the audio stream hasn't been
* instantiated.
*/
public int getAudioSessionId() {
return audioPlayerImpl.getAudioSessionId();
}
/**
* Sets the playback speed for this MediaPlayer.
*
* @param speed The speed to play the media back at
* @return True if the speed was set
*/
public boolean setPlaybackSpeed(float speed) {
return audioPlayerImpl.setPlaybackSpeed(speed);
}
/**
* Sets the audio stream type for this MediaPlayer. See {@link AudioManager}
* for a list of stream types. Must call this method before prepare() or
* prepareAsync() in order for the target stream type to become effective
* thereafter.
*
* @param streamType The audio stream type
* @see android.media.AudioManager
*/
public void setAudioStreamType(int streamType) {
audioPlayerImpl.setAudioStreamType(streamType);
}
/**
* Sets the source path for the audio item. This path can be a web address (e.g. http://) or
* an absolute local path (e.g. file://)
*
* @param uri The Uri representing the path to the audio item
*/
public void setDataSource(@Nullable Uri uri) {
audioPlayerImpl.setDataSource(uri);
overrideDuration(-1);
}
/**
* Sets the source path for the audio item. This path can be a web address (e.g. http://) or
* an absolute local path (e.g. file://)
*
* @param uri The Uri representing the path to the audio item
* @param mediaSource The MediaSource to use for audio playback
*/
public void setDataSource(@Nullable Uri uri, @Nullable MediaSource mediaSource) {
audioPlayerImpl.setDataSource(uri, mediaSource);
overrideDuration(-1);
}
/**
* Sets the {@link MediaDrmCallback} to use when handling DRM for media.
* This should be called before specifying the videos uri or path
* <br>
* <b>NOTE:</b> DRM is only supported on API 18 +
*
* @param drmCallback The callback to use when handling DRM media
*/
public void setDrmCallback(@Nullable MediaDrmCallback drmCallback) {
audioPlayerImpl.setDrmCallback(drmCallback);
}
/**
* Prepares the media specified with {@link #setDataSource(Uri)} or
* {@link #setDataSource(Uri, MediaSource)} in an asynchronous manner
*/
public void prepareAsync() {
audioPlayerImpl.prepareAsync();
}
/**
* Sets the volume level for the audio playback.
*
* @param leftVolume The volume range [0.0 - 1.0]
* @param rightVolume The volume range [0.0 - 1.0]
*/
public void setVolume(@FloatRange(from = 0.0, to = 1.0) float leftVolume, @FloatRange(from = 0.0, to = 1.0) float rightVolume) {
audioPlayerImpl.setVolume(leftVolume, rightVolume);
}
/**
* Set the low-level power management behavior for this AudioPlayer.
*
* <p>This function has the AudioPlayer access the low-level power manager
* service to control the device's power usage while playing is occurring.
* The parameter is a combination of {@link android.os.PowerManager} wake flags.
* Use of this method requires {@link android.Manifest.permission#WAKE_LOCK}
* permission.
* By default, no attempt is made to keep the device awake during playback.
*
* @param context the Context to use
* @param mode the power/wake mode to set
* @see android.os.PowerManager
*/
public void setWakeMode(Context context, int mode) {
audioPlayerImpl.setWakeMode(context, mode);
}
/**
* Stops the current audio playback and resets the listener states
* so that we receive the callbacks for events like onPrepared
*/
public void reset() {
stopPlayback();
setDataSource(null, null);
audioPlayerImpl.reset();
}
/**
* Moves the current audio progress to the specified location.
* This method should only be called after the AudioPlayer is
* prepared. (see {@link #setOnPreparedListener(OnPreparedListener)}
*
* @param milliSeconds The time to move the playback to
*/
public void seekTo(long milliSeconds) {
audioPlayerImpl.seekTo(milliSeconds);
}
/**
* Returns if an audio item is currently in playback
*
* @return True if an audio item is playing
*/
public boolean isPlaying() {
return audioPlayerImpl.isPlaying();
}
/**
* Starts the playback for the audio item specified in {@link #setDataSource(Uri)}.
* This should be called after the AudioPlayer is correctly prepared (see {@link #setOnPreparedListener(OnPreparedListener)})
*/
public void start() {
audioPlayerImpl.start();
}
/**
* If an audio item is currently in playback, it will be paused
*/
public void pause() {
audioPlayerImpl.pause();
}
/**
* If an audio item is currently in playback then the playback will be stopped
*/
public void stopPlayback() {
audioPlayerImpl.stopPlayback();
}
/**
* Releases the resources associated with this media player
*/
public void release() {
audioPlayerImpl.release();
}
/**
* Retrieves the duration of the current audio item. This should only be called after
* the item is prepared (see {@link #setOnPreparedListener(OnPreparedListener)}).
* If {@link #overrideDuration(long)} is set then that value will be returned.
*
* @return The millisecond duration of the video
*/
public long getDuration() {
if (overriddenDuration >= 0) {
return overriddenDuration;
}
return audioPlayerImpl.getDuration();
}
/**
* Setting this will override the duration that the item may actually be. This method should
* only be used when the item doesn't return the correct duration such as with audio streams.
* This only overrides the current audio item.
*
* @param duration The duration for the current media item or < 0 to disable
*/
public void overrideDuration(long duration) {
overriddenDuration = duration;
}
/**
* Retrieves the current position of the audio playback. If an audio item is not currently
* in playback then the value will be 0. This should only be called after the item is
* prepared (see {@link #setOnPreparedListener(OnPreparedListener)})
*
* @return The millisecond value for the current position
*/
public long getCurrentPosition() {
return audioPlayerImpl.getCurrentPosition();
}
/**
* Retrieves the current buffer percent of the audio item. If an audio item is not currently
* prepared or buffering the value will be 0. This should only be called after the audio item is
* prepared (see {@link #setOnPreparedListener(OnPreparedListener)})
*
* @return The integer percent that is buffered [0, 100] inclusive
*/
public int getBufferPercentage() {
return audioPlayerImpl.getBufferedPercent();
}
/**
* Determines if the current video player implementation supports
* track selection for audio or video tracks.
*
* @return True if tracks can be manually specified
*/
public boolean trackSelectionAvailable() {
return audioPlayerImpl.trackSelectionAvailable();
}
/**
* Changes to the track with <code>trackIndex</code> for the specified
* <code>trackType</code>
*
* @param trackType The type for the track to switch to the selected index
* @param trackIndex The index for the track to switch to
*/
public void setTrack(ExoMedia.RendererType trackType, int trackIndex) {
audioPlayerImpl.setTrack(trackType, trackIndex);
}
/**
* Retrieves a list of available tracks to select from. Typically {@link #trackSelectionAvailable()}
* should be called before this.
*
* @return A list of available tracks associated with each track type
*/
@Nullable
public Map<ExoMedia.RendererType, TrackGroupArray> getAvailableTracks() {
return audioPlayerImpl.getAvailableTracks();
}
/**
* Sets the listener to inform of VideoPlayer prepared events
*
* @param listener The listener
*/
public void setOnPreparedListener(@Nullable OnPreparedListener listener) {
listenerMux.setOnPreparedListener(listener);
}
/**
* Sets the listener to inform of VideoPlayer completion events
*
* @param listener The listener
*/
public void setOnCompletionListener(@Nullable OnCompletionListener listener) {
listenerMux.setOnCompletionListener(listener);
}
/**
* Sets the listener to inform of VideoPlayer buffer update events
*
* @param listener The listener
*/
public void setOnBufferUpdateListener(@Nullable OnBufferUpdateListener listener) {
listenerMux.setOnBufferUpdateListener(listener);
}
/**
* Sets the listener to inform of VideoPlayer seek completion events
*
* @param listener The listener
*/
public void setOnSeekCompletionListener(@Nullable OnSeekCompletionListener listener) {
listenerMux.setOnSeekCompletionListener(listener);
}
/**
* Sets the listener to inform of playback errors
*
* @param listener The listener
*/
public void setOnErrorListener(@Nullable OnErrorListener listener) {
listenerMux.setOnErrorListener(listener);
}
/**
* Sets the listener to inform of ID3 metadata updates
*
* @param listener The listener to inform
*/
public void setMetadataListener(@Nullable MetadataListener listener) {
listenerMux.setMetadataListener(listener);
}
/**
* Performs the functionality to stop the progress polling, and stop any other
* procedures from running that we no longer need.
*/
private void onPlaybackEnded() {
pause();
}
private class MuxNotifier extends ListenerMux.Notifier {
@Override
public boolean shouldNotifyCompletion(long endLeeway) {
return getCurrentPosition() + endLeeway >= getDuration();
}
@Override
public void onExoPlayerError(ExoMediaPlayer exoMediaPlayer, Exception e) {
stopPlayback();
if (exoMediaPlayer != null) {
exoMediaPlayer.forcePrepare();
}
}
@Override
public void onMediaPlaybackEnded() {
onPlaybackEnded();
}
@Override
public void onPrepared() {
audioPlayerImpl.onMediaPrepared();
}
}
}