/*
* ServeStream: A HTTP stream browser/player for Android
* Copyright 2014 William Seemann
*
* 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 net.sourceforge.servestream.media;
import android.content.Context;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import net.sourceforge.servestream.transport.File;
import net.sourceforge.servestream.transport.HTTP;
import net.sourceforge.servestream.transport.HTTPS;
import net.sourceforge.servestream.transport.MMS;
import net.sourceforge.servestream.transport.MMSH;
import net.sourceforge.servestream.transport.MMST;
import net.sourceforge.servestream.transport.RTSP;
import net.sourceforge.servestream.utils.HTTPRequestTask;
import net.sourceforge.servestream.utils.HTTPRequestTask.HTTPRequestListener;
import net.sourceforge.servestream.utils.URLUtils;
import net.sourceforge.servestream.service.MediaPlaybackService;
/**
* Provides a unified interface for dealing with media files.
*/
public final class MultiPlayer implements HTTPRequestListener {
private static final String TAG = MultiPlayer.class.getName();
private Handler mHandler;
private NativePlayer mNativeMediaPlayer = new NativePlayer();
private DownloadPlayer mDownloadMediaPlayer;
private FFmpegMediaPlayer mFFmpegMediaPlayer;
private AbstractMediaPlayer mMediaPlayer = mNativeMediaPlayer;
private boolean mIsInitialized = false;
/**
* Default constructor
*/
protected MultiPlayer() {
}
public MultiPlayer(Context context) {
}
public void setDataSource(Context context, long id) {
setDataSource(context, null, id, true, false, null);
}
public void setDataSource(String path, boolean useFFmpegPlayer) {
setDataSource(null, path, -1, false, useFFmpegPlayer, null);
}
private void setDataSource(Context context, String path, long id, boolean isLocalFile, boolean useFFmpegPlayer, String contentType) {
try {
mMediaPlayer.reset();
if (!isLocalFile && contentType == null && path.startsWith(HTTP.getProtocolName())) {
new HTTPRequestTask(path, useFFmpegPlayer, this).execute();
return;
}
AbstractMediaPlayer player = null;
if (isLocalFile) {
player = getDownloadPlayer();
} else {
if (useFFmpegPlayer) {
player = getFFmpegPlayer();
} else {
player = getMediaPlayer(path);
}
}
mMediaPlayer = player;
mMediaPlayer.reset();
mMediaPlayer.setOnPreparedListener(onPreparedListener);
mMediaPlayer.setOnCompletionListener(onCompletionListener);
mMediaPlayer.setOnErrorListener(onErrorListener);
mMediaPlayer.setOnInfoListener(onInfoListener);
if (isLocalFile) {
mMediaPlayer.setDataSource(context, id);
mMediaPlayer.prepareAsync();
} else {
mMediaPlayer.setDataSource(URLUtils.encodeURL(path));
mMediaPlayer.prepareAsync();
}
Log.v(TAG, "Preparing media player");
} catch (IOException ex) {
Log.v(TAG, "Error initializing media player");
mIsInitialized = false;
sendErrorMessage(0);
} catch (IllegalArgumentException ex) {
Log.v(TAG, "Error initializing media player");
mIsInitialized = false;
sendErrorMessage(0);
}
}
public boolean isInitialized() {
return mIsInitialized;
}
public void start() {
mMediaPlayer.start();
}
public void stop() {
mMediaPlayer.reset();
mIsInitialized = false;
}
public void release() {
stop();
mMediaPlayer.release();
if (mNativeMediaPlayer != null) {
mNativeMediaPlayer.release();
mNativeMediaPlayer = null;
}
if (mDownloadMediaPlayer != null) {
mDownloadMediaPlayer.release();
mDownloadMediaPlayer = null;
}
if (mFFmpegMediaPlayer != null) {
mFFmpegMediaPlayer.release();
mFFmpegMediaPlayer = null;
}
}
public void pause() {
mMediaPlayer.pause();
}
public void setHandler(Handler handler) {
mHandler = handler;
}
private AbstractMediaPlayer.OnPreparedListener onPreparedListener = new AbstractMediaPlayer.OnPreparedListener() {
public void onPrepared(AbstractMediaPlayer mp) {
Log.i(TAG, "onPreparedListener called");
mIsInitialized = true;
mHandler.sendEmptyMessage(MediaPlaybackService.PREPARED);
}
};
private AbstractMediaPlayer.OnCompletionListener onCompletionListener = new AbstractMediaPlayer.OnCompletionListener() {
public void onCompletion(AbstractMediaPlayer mp) {
Log.i(TAG, "onCompletionListener called");
if (mIsInitialized) {
mHandler.sendEmptyMessage(MediaPlaybackService.TRACK_ENDED);
}
}
};
private AbstractMediaPlayer.OnErrorListener onErrorListener = new AbstractMediaPlayer.OnErrorListener() {
public boolean onError(AbstractMediaPlayer mp, int what, int extra) {
Log.i(TAG, "onErrorListener called");
Log.d(TAG, "Error: " + what + "," + extra);
switch (what) {
case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
release();
mNativeMediaPlayer = new NativePlayer();
mMediaPlayer = mNativeMediaPlayer;
sendErrorMessage(MediaPlaybackService.SERVER_DIED);
return true;
default:
mIsInitialized = false;
sendErrorMessage(0);
break;
}
return false;
}
};
private AbstractMediaPlayer.OnInfoListener onInfoListener = new AbstractMediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(AbstractMediaPlayer mp, int what, int extra) {
switch (what) {
case AbstractMediaPlayer.MEDIA_INFO_METADATA_UPDATE:
mHandler.sendEmptyMessage(MediaPlaybackService.INFO);
return true;
default:
break;
}
return false;
}
};
public long duration() {
return mMediaPlayer.getDuration();
}
public long position() {
return mMediaPlayer.getCurrentPosition();
}
public long seek(long msec) {
mMediaPlayer.seekTo((int) msec);
return msec;
}
public void setVolume(float vol) {
mMediaPlayer.setVolume(vol, vol);
}
public void setAudioSessionId(int sessionId) {
mMediaPlayer.setAudioSessionId(sessionId);
}
public int getAudioSessionId() {
return mMediaPlayer.getAudioSessionId();
}
public void setNextDataSource(String path) {
}
/**
* Detects the appropriate media player depending on the URI of
* a file.
* @param uri path to a file.
* @return a media player.
*/
private AbstractMediaPlayer getMediaPlayer(String uri) {
if (uri.startsWith(HTTP.getProtocolName())) {
return mNativeMediaPlayer;
} else if (uri.startsWith(HTTPS.getProtocolName())) {
return mNativeMediaPlayer;
} else if (uri.startsWith(File.getProtocolName())) {
return mNativeMediaPlayer;
} else if (uri.startsWith(RTSP.getProtocolName())) {
return mNativeMediaPlayer;
} else if (uri.startsWith(MMS.getProtocolName())) {
return getFFmpegPlayer();
} else if (uri.startsWith(MMSH.getProtocolName())) {
return getFFmpegPlayer();
} else if (uri.startsWith(MMST.getProtocolName())) {
return getFFmpegPlayer();
} else {
return mNativeMediaPlayer;
}
}
private DownloadPlayer getDownloadPlayer() {
// allow for lazy initialization of Download player
// in case it is never used
if (mDownloadMediaPlayer == null) {
mDownloadMediaPlayer = new DownloadPlayer();
}
return mDownloadMediaPlayer;
}
private FFmpegMediaPlayer getFFmpegPlayer() {
// allow for lazy initialization of FFmpeg player
// in case it is never used
if (mFFmpegMediaPlayer == null) {
mFFmpegMediaPlayer = new FFmpegMediaPlayer();
}
return mFFmpegMediaPlayer;
}
@Override
public void onContentTypeObtained(String path, boolean useFFmpegPlayer,
String contentType) {
if (contentType.equalsIgnoreCase("video/x-ms-asf") ||
contentType.equalsIgnoreCase("application/vnd.ms-asf")) {
path = path.replace(HTTP.getProtocolName(), MMSH.getProtocolName());
} else if (contentType.equals("audio/aacp") ||
contentType.equals("audio/3gpp") ||
contentType.equals("audio/3gpp2")) {
useFFmpegPlayer = true;
}
setDataSource(null, path, -1, false, useFFmpegPlayer, contentType);
}
@Override
public void onHTTPRequestError(String path, boolean useFFmpegPlayer) {
setDataSource(null, path, -1, false, useFFmpegPlayer, "");
}
private void sendErrorMessage(int arg1) {
Message message = mHandler.obtainMessage();
message.arg1 = arg1;
mHandler.sendMessage(message);
}
}