/*
* Copyright (C) 2006 The Android Open Source Project
* Copyright (C) 2013 Zhang Rui <bbcallen@gmail.com>
*
* 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 tv.danmaku.ijk.media.player;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.SurfaceTexture;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
import tv.danmaku.ijk.media.player.annotations.AccessedByNative;
import tv.danmaku.ijk.media.player.annotations.CalledByNative;
import tv.danmaku.ijk.media.player.misc.IMediaDataSource;
import tv.danmaku.ijk.media.player.misc.ITrackInfo;
import tv.danmaku.ijk.media.player.misc.IjkTrackInfo;
import tv.danmaku.ijk.media.player.pragma.DebugLog;
/**
* @author bbcallen
*
* Java wrapper of ffplay.
*/
public final class IjkMediaPlayer extends AbstractMediaPlayer {
private final static String TAG = IjkMediaPlayer.class.getName();
/**播放器拥有一个同样的ID**/
public static final int FULLSCREEN_ID = 33797;
private static final int MEDIA_NOP = 0; // interface test message
private static final int MEDIA_PREPARED = 1;
private static final int MEDIA_PLAYBACK_COMPLETE = 2;
private static final int MEDIA_BUFFERING_UPDATE = 3;
private static final int MEDIA_SEEK_COMPLETE = 4;
private static final int MEDIA_SET_VIDEO_SIZE = 5;
private static final int MEDIA_TIMED_TEXT = 99;
private static final int MEDIA_ERROR = 100;
private static final int MEDIA_INFO = 200;
protected static final int MEDIA_SET_VIDEO_SAR = 10001;
//----------------------------------------
// options
public static final int IJK_LOG_UNKNOWN = 0;
public static final int IJK_LOG_DEFAULT = 1;
public static final int IJK_LOG_VERBOSE = 2;
public static final int IJK_LOG_DEBUG = 3;
public static final int IJK_LOG_INFO = 4;
public static final int IJK_LOG_WARN = 5;
public static final int IJK_LOG_ERROR = 6;
public static final int IJK_LOG_FATAL = 7;
public static final int IJK_LOG_SILENT = 8;
public static final int OPT_CATEGORY_FORMAT = 1;
public static final int OPT_CATEGORY_CODEC = 2;
public static final int OPT_CATEGORY_SWS = 3;
public static final int OPT_CATEGORY_PLAYER = 4;
public static final int SDL_FCC_YV12 = 0x32315659; // YV12
public static final int SDL_FCC_RV16 = 0x36315652; // RGB565
public static final int SDL_FCC_RV32 = 0x32335652; // RGBX8888
//----------------------------------------
//----------------------------------------
// properties
public static final int PROP_FLOAT_VIDEO_DECODE_FRAMES_PER_SECOND = 10001;
public static final int PROP_FLOAT_VIDEO_OUTPUT_FRAMES_PER_SECOND = 10002;
public static final int FFP_PROP_FLOAT_PLAYBACK_RATE = 10003;
public static final int FFP_PROP_INT64_SELECTED_VIDEO_STREAM = 20001;
public static final int FFP_PROP_INT64_SELECTED_AUDIO_STREAM = 20002;
public static final int FFP_PROP_INT64_VIDEO_DECODER = 20003;
public static final int FFP_PROP_INT64_AUDIO_DECODER = 20004;
public static final int FFP_PROPV_DECODER_UNKNOWN = 0;
public static final int FFP_PROPV_DECODER_AVCODEC = 1;
public static final int FFP_PROPV_DECODER_MEDIACODEC = 2;
public static final int FFP_PROPV_DECODER_VIDEOTOOLBOX = 3;
public static final int FFP_PROP_INT64_VIDEO_CACHED_DURATION = 20005;
public static final int FFP_PROP_INT64_AUDIO_CACHED_DURATION = 20006;
public static final int FFP_PROP_INT64_VIDEO_CACHED_BYTES = 20007;
public static final int FFP_PROP_INT64_AUDIO_CACHED_BYTES = 20008;
public static final int FFP_PROP_INT64_VIDEO_CACHED_PACKETS = 20009;
public static final int FFP_PROP_INT64_AUDIO_CACHED_PACKETS = 20010;
//----------------------------------------
@AccessedByNative
private long mNativeMediaPlayer;
@AccessedByNative
private long mNativeMediaDataSource;
@AccessedByNative
private int mNativeSurfaceTexture;
@AccessedByNative
private int mListenerContext;
private SurfaceHolder mSurfaceHolder;
private EventHandler mEventHandler;
private PowerManager.WakeLock mWakeLock = null;
private boolean mScreenOnWhilePlaying;
private boolean mStayAwake;
private int mVideoWidth;
private int mVideoHeight;
private int mVideoSarNum;
private int mVideoSarDen;
private String mDataSource;
/**
* Default library loader
* Load them by yourself, if your libraries are not installed at default place.
*/
private static final IjkLibLoader sLocalLibLoader = new IjkLibLoader() {
@Override
public void loadLibrary(String libName) throws UnsatisfiedLinkError, SecurityException {
System.loadLibrary(libName);
}
};
private static volatile boolean mIsLibLoaded = false;
public static void loadLibrariesOnce(IjkLibLoader libLoader) {
synchronized (IjkMediaPlayer.class) {
if (!mIsLibLoaded) {
if (libLoader == null)
libLoader = sLocalLibLoader;
libLoader.loadLibrary("ijkffmpeg");
libLoader.loadLibrary("ijksdl");
libLoader.loadLibrary("ijkplayer");
mIsLibLoaded = true;
}
}
}
private static volatile boolean mIsNativeInitialized = false;
private static void initNativeOnce() {
synchronized (IjkMediaPlayer.class) {
if (!mIsNativeInitialized) {
native_init();
mIsNativeInitialized = true;
}
}
}
/**
* Default constructor. Consider using one of the create() methods for
* synchronously instantiating a IjkMediaPlayer from a Uri or resource.
* <p>
* When done with the IjkMediaPlayer, you should call {@link #release()}, to
* free the resources. If not released, too many IjkMediaPlayer instances
* may result in an exception.
* </p>
*/
public IjkMediaPlayer() {
this(sLocalLibLoader);
}
/**
* do not loadLibaray
* @param libLoader
* custom library loader, can be null.
*/
public IjkMediaPlayer(IjkLibLoader libLoader) {
initPlayer(libLoader);
}
private void initPlayer(IjkLibLoader libLoader) {
loadLibrariesOnce(libLoader);
initNativeOnce();
Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
/*
* Native setup requires a weak reference to our object. It's easier to
* create it here than in C++.
*/
native_setup(new WeakReference<IjkMediaPlayer>(this));
}
/*
* Update the IjkMediaPlayer SurfaceTexture. Call after setting a new
* display surface.
*/
private native void _setVideoSurface(Surface surface);
/**
* Sets the {@link SurfaceHolder} to use for displaying the video portion of
* the media.
*
* Either a surface holder or surface must be set if a display or video sink
* is needed. Not calling this method or {@link #setSurface(Surface)} when
* playing back a video will result in only the audio track being played. A
* null surface holder or surface will result in only the audio track being
* played.
*
* @param sh
* the SurfaceHolder to use for video display
*/
@Override
public void setDisplay(SurfaceHolder sh) {
mSurfaceHolder = sh;
Surface surface;
if (sh != null) {
surface = sh.getSurface();
} else {
surface = null;
}
_setVideoSurface(surface);
updateSurfaceScreenOn();
}
/**
* Sets the {@link Surface} to be used as the sink for the video portion of
* the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but
* does not support {@link #setScreenOnWhilePlaying(boolean)}. Setting a
* Surface will un-set any Surface or SurfaceHolder that was previously set.
* A null surface will result in only the audio track being played.
*
* If the Surface sends frames to a {@link SurfaceTexture}, the timestamps
* returned from {@link SurfaceTexture#getTimestamp()} will have an
* unspecified zero point. These timestamps cannot be directly compared
* between different media sources, different instances of the same media
* source, or multiple runs of the same program. The timestamp is normally
* monotonically increasing and is unaffected by time-of-day adjustments,
* but it is reset when the position is set.
*
* @param surface
* The {@link Surface} to be used for the video portion of the
* media.
*/
@Override
public void setSurface(Surface surface) {
if (mScreenOnWhilePlaying && surface != null) {
DebugLog.w(TAG,
"setScreenOnWhilePlaying(true) is ineffective for Surface");
}
mSurfaceHolder = null;
_setVideoSurface(surface);
updateSurfaceScreenOn();
}
/**
* Sets the data source as a content Uri.
*
* @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
* @throws IllegalStateException if it is called in an invalid state
*/
@Override
public void setDataSource(Context context, Uri uri)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
setDataSource(context, uri, null);
}
/**
* Sets the data source as a content Uri.
*
* @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
* @param headers the headers to be sent together with the request for the data
* Note that the cross domain redirection is allowed by default, but that can be
* changed with key/value pairs through the headers parameter with
* "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
* to disallow or allow cross domain redirection.
* @throws IllegalStateException if it is called in an invalid state
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void setDataSource(Context context, Uri uri, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
final String scheme = uri.getScheme();
if (ContentResolver.SCHEME_FILE.equals(scheme)) {
setDataSource(uri.getPath());
return;
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
&& Settings.AUTHORITY.equals(uri.getAuthority())) {
// Redirect ringtones to go directly to underlying provider
uri = RingtoneManager.getActualDefaultRingtoneUri(context,
RingtoneManager.getDefaultType(uri));
if (uri == null) {
throw new FileNotFoundException("Failed to resolve default ringtone");
}
}
AssetFileDescriptor fd = null;
try {
ContentResolver resolver = context.getContentResolver();
fd = resolver.openAssetFileDescriptor(uri, "r");
if (fd == null) {
return;
}
// Note: using getDeclaredLength so that our behavior is the same
// as previous versions when the content provider is returning
// a full file.
if (fd.getDeclaredLength() < 0) {
setDataSource(fd.getFileDescriptor());
} else {
setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getDeclaredLength());
}
return;
} catch (SecurityException ignored) {
} catch (IOException ignored) {
} finally {
if (fd != null) {
fd.close();
}
}
Log.d(TAG, "Couldn't open file on client side, trying server side");
setDataSource(uri.toString(), headers);
}
/**
* Sets the data source (file-path or http/rtsp URL) to use.
*
* @param path
* the path of the file, or the http/rtsp URL of the stream you
* want to play
* @throws IllegalStateException
* if it is called in an invalid state
*
* <p>
* When <code>path</code> refers to a local file, the file may
* actually be opened by a process other than the calling
* application. This implies that the pathname should be an
* absolute path (as any other process runs with unspecified
* current working directory), and that the pathname should
* reference a world-readable file.
*/
@Override
public void setDataSource(String path)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
mDataSource = path;
_setDataSource(path, null, null);
}
/**
* Sets the data source (file-path or http/rtsp URL) to use.
*
* @param path the path of the file, or the http/rtsp URL of the stream you want to play
* @param headers the headers associated with the http request for the stream you want to play
* @throws IllegalStateException if it is called in an invalid state
*/
public void setDataSource(String path, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException
{
if (headers != null && !headers.isEmpty()) {
StringBuilder sb = new StringBuilder();
for(Map.Entry<String, String> entry: headers.entrySet()) {
sb.append(entry.getKey());
sb.append(":");
String value = entry.getValue();
if (!TextUtils.isEmpty(value))
sb.append(entry.getValue());
sb.append("\r\n");
setOption(OPT_CATEGORY_FORMAT, "headers", sb.toString());
}
}
setDataSource(path);
}
/**
* Sets the data source (FileDescriptor) to use. It is the caller's responsibility
* to close the file descriptor. It is safe to do so as soon as this call returns.
*
* @param fd the FileDescriptor for the file you want to play
* @throws IllegalStateException if it is called in an invalid state
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
@Override
public void setDataSource(FileDescriptor fd)
throws IOException, IllegalArgumentException, IllegalStateException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) {
int native_fd = -1;
try {
Field f = fd.getClass().getDeclaredField("descriptor"); //NoSuchFieldException
f.setAccessible(true);
native_fd = f.getInt(fd); //IllegalAccessException
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
_setDataSourceFd(native_fd);
} else {
ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd);
try {
_setDataSourceFd(pfd.getFd());
} finally {
pfd.close();
}
}
}
/**
* Sets the data source (FileDescriptor) to use. The FileDescriptor must be
* seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
* to close the file descriptor. It is safe to do so as soon as this call returns.
*
* @param fd the FileDescriptor for the file you want to play
* @param offset the offset into the file where the data to be played starts, in bytes
* @param length the length in bytes of the data to be played
* @throws IllegalStateException if it is called in an invalid state
*/
private void setDataSource(FileDescriptor fd, long offset, long length)
throws IOException, IllegalArgumentException, IllegalStateException {
// FIXME: handle offset, length
setDataSource(fd);
}
public void setDataSource(IMediaDataSource mediaDataSource)
throws IllegalArgumentException, SecurityException, IllegalStateException {
_setDataSource(mediaDataSource);
}
private native void _setDataSource(String path, String[] keys, String[] values)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
private native void _setDataSourceFd(int fd)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
private native void _setDataSource(IMediaDataSource mediaDataSource)
throws IllegalArgumentException, SecurityException, IllegalStateException;
@Override
public String getDataSource() {
return mDataSource;
}
@Override
public void prepareAsync() throws IllegalStateException {
_prepareAsync();
}
public native void _prepareAsync() throws IllegalStateException;
@Override
public void start() throws IllegalStateException {
stayAwake(true);
_start();
}
private native void _start() throws IllegalStateException;
@Override
public void stop() throws IllegalStateException {
stayAwake(false);
_stop();
}
private native void _stop() throws IllegalStateException;
@Override
public void pause() throws IllegalStateException {
stayAwake(false);
_pause();
}
private native void _pause() throws IllegalStateException;
@SuppressLint("Wakelock")
@Override
public void setWakeMode(Context context, int mode) {
boolean washeld = false;
if (mWakeLock != null) {
if (mWakeLock.isHeld()) {
washeld = true;
mWakeLock.release();
}
mWakeLock = null;
}
PowerManager pm = (PowerManager) context
.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(mode | PowerManager.ON_AFTER_RELEASE,
IjkMediaPlayer.class.getName());
mWakeLock.setReferenceCounted(false);
if (washeld) {
mWakeLock.acquire();
}
}
@Override
public void setScreenOnWhilePlaying(boolean screenOn) {
if (mScreenOnWhilePlaying != screenOn) {
if (screenOn && mSurfaceHolder == null) {
DebugLog.w(TAG,
"setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder");
}
mScreenOnWhilePlaying = screenOn;
updateSurfaceScreenOn();
}
}
@SuppressLint("Wakelock")
private void stayAwake(boolean awake) {
if (mWakeLock != null) {
if (awake && !mWakeLock.isHeld()) {
mWakeLock.acquire();
} else if (!awake && mWakeLock.isHeld()) {
mWakeLock.release();
}
}
mStayAwake = awake;
updateSurfaceScreenOn();
}
private void updateSurfaceScreenOn() {
if (mSurfaceHolder != null) {
mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
}
}
@Override
public IjkTrackInfo[] getTrackInfo() {
Bundle bundle = getMediaMeta();
if (bundle == null)
return null;
IjkMediaMeta mediaMeta = IjkMediaMeta.parse(bundle);
if (mediaMeta == null || mediaMeta.mStreams == null)
return null;
ArrayList<IjkTrackInfo> trackInfos = new ArrayList<IjkTrackInfo>();
for (IjkMediaMeta.IjkStreamMeta streamMeta: mediaMeta.mStreams) {
IjkTrackInfo trackInfo = new IjkTrackInfo(streamMeta);
if (streamMeta.mType.equalsIgnoreCase(IjkMediaMeta.IJKM_VAL_TYPE__VIDEO)) {
trackInfo.setTrackType(ITrackInfo.MEDIA_TRACK_TYPE_VIDEO);
} else if (streamMeta.mType.equalsIgnoreCase(IjkMediaMeta.IJKM_VAL_TYPE__AUDIO)) {
trackInfo.setTrackType(ITrackInfo.MEDIA_TRACK_TYPE_AUDIO);
}
trackInfos.add(trackInfo);
}
return trackInfos.toArray(new IjkTrackInfo[trackInfos.size()]);
}
// TODO: @Override
public int getSelectedTrack(int trackType) {
switch (trackType) {
case ITrackInfo.MEDIA_TRACK_TYPE_VIDEO:
return (int)_getPropertyLong(FFP_PROP_INT64_SELECTED_VIDEO_STREAM, -1);
case ITrackInfo.MEDIA_TRACK_TYPE_AUDIO:
return (int)_getPropertyLong(FFP_PROP_INT64_SELECTED_AUDIO_STREAM, -1);
default:
return -1;
}
}
// experimental, should set DEFAULT_MIN_FRAMES and MAX_MIN_FRAMES to 25
// TODO: @Override
public void selectTrack(int track) {
_setStreamSelected(track, true);
}
// experimental, should set DEFAULT_MIN_FRAMES and MAX_MIN_FRAMES to 25
// TODO: @Override
public void deselectTrack(int track) {
_setStreamSelected(track, false);
}
private native void _setStreamSelected(int stream, boolean select);
@Override
public int getVideoWidth() {
return mVideoWidth;
}
@Override
public int getVideoHeight() {
return mVideoHeight;
}
@Override
public int getVideoSarNum() {
return mVideoSarNum;
}
@Override
public int getVideoSarDen() {
return mVideoSarDen;
}
@Override
public native boolean isPlaying();
@Override
public native void seekTo(long msec) throws IllegalStateException;
@Override
public native long getCurrentPosition();
@Override
public native long getDuration();
/**
* Releases resources associated with this IjkMediaPlayer object. It is
* considered good practice to call this method when you're done using the
* IjkMediaPlayer. In particular, whenever an Activity of an application is
* paused (its onPause() method is called), or stopped (its onStop() method
* is called), this method should be invoked to release the IjkMediaPlayer
* object, unless the application has a special need to keep the object
* around. In addition to unnecessary resources (such as memory and
* instances of codecs) being held, failure to call this method immediately
* if a IjkMediaPlayer object is no longer needed may also lead to
* continuous battery consumption for mobile devices, and playback failure
* for other applications if no multiple instances of the same codec are
* supported on a device. Even if multiple instances of the same codec are
* supported, some performance degradation may be expected when unnecessary
* multiple instances are used at the same time.
*/
@Override
public void release() {
stayAwake(false);
updateSurfaceScreenOn();
resetListeners();
_release();
}
private native void _release();
@Override
public void reset() {
stayAwake(false);
_reset();
// make sure none of the listeners get called anymore
mEventHandler.removeCallbacksAndMessages(null);
mVideoWidth = 0;
mVideoHeight = 0;
}
private native void _reset();
/**
* Sets the player to be looping or non-looping.
*
* @param looping whether to loop or not
*/
@Override
public void setLooping(boolean looping) {
int loopCount = looping ? 0 : 1;
setOption(OPT_CATEGORY_PLAYER, "loop", loopCount);
_setLoopCount(loopCount);
}
private native void _setLoopCount(int loopCount);
/**
* Checks whether the MediaPlayer is looping or non-looping.
*
* @return true if the MediaPlayer is currently looping, false otherwise
*/
@Override
public boolean isLooping() {
int loopCount = _getLoopCount();
return loopCount != 1;
}
private native int _getLoopCount();
@TargetApi(Build.VERSION_CODES.M)
public void setSpeed(float speed) {
_setPropertyFloat(FFP_PROP_FLOAT_PLAYBACK_RATE, speed);
}
@TargetApi(Build.VERSION_CODES.M)
public float getSpeed(float speed) {
return _getPropertyFloat(FFP_PROP_FLOAT_PLAYBACK_RATE, .0f);
}
public int getVideoDecoder() {
return (int)_getPropertyLong(FFP_PROP_INT64_VIDEO_DECODER, FFP_PROPV_DECODER_UNKNOWN);
}
public float getVideoOutputFramesPerSecond() {
return _getPropertyFloat(PROP_FLOAT_VIDEO_OUTPUT_FRAMES_PER_SECOND, 0.0f);
}
public float getVideoDecodeFramesPerSecond() {
return _getPropertyFloat(PROP_FLOAT_VIDEO_DECODE_FRAMES_PER_SECOND, 0.0f);
}
public long getVideoCachedDuration() {
return _getPropertyLong(FFP_PROP_INT64_VIDEO_CACHED_DURATION, 0);
}
public long getAudioCachedDuration() {
return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_DURATION, 0);
}
public long getVideoCachedBytes() {
return _getPropertyLong(FFP_PROP_INT64_VIDEO_CACHED_BYTES, 0);
}
public long getAudioCachedBytes() {
return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_BYTES, 0);
}
public long getVideoCachedPackets() {
return _getPropertyLong(FFP_PROP_INT64_VIDEO_CACHED_PACKETS, 0);
}
public long getAudioCachedPackets() {
return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_PACKETS, 0);
}
private native float _getPropertyFloat(int property, float defaultValue);
private native void _setPropertyFloat(int property, float value);
private native long _getPropertyLong(int property, long defaultValue);
private native void _setPropertyLong(int property, long value);
@Override
public native void setVolume(float leftVolume, float rightVolume);
@Override
public native int getAudioSessionId();
@Override
public MediaInfo getMediaInfo() {
MediaInfo mediaInfo = new MediaInfo();
mediaInfo.mMediaPlayerName = "ijkplayer";
String videoCodecInfo = _getVideoCodecInfo();
if (!TextUtils.isEmpty(videoCodecInfo)) {
String nodes[] = videoCodecInfo.split(",");
if (nodes.length >= 2) {
mediaInfo.mVideoDecoder = nodes[0];
mediaInfo.mVideoDecoderImpl = nodes[1];
} else if (nodes.length >= 1) {
mediaInfo.mVideoDecoder = nodes[0];
mediaInfo.mVideoDecoderImpl = "";
}
}
String audioCodecInfo = _getAudioCodecInfo();
if (!TextUtils.isEmpty(audioCodecInfo)) {
String nodes[] = audioCodecInfo.split(",");
if (nodes.length >= 2) {
mediaInfo.mAudioDecoder = nodes[0];
mediaInfo.mAudioDecoderImpl = nodes[1];
} else if (nodes.length >= 1) {
mediaInfo.mAudioDecoder = nodes[0];
mediaInfo.mAudioDecoderImpl = "";
}
}
try {
mediaInfo.mMeta = IjkMediaMeta.parse(_getMediaMeta());
} catch (Throwable e) {
e.printStackTrace();
}
return mediaInfo;
}
@Override
public void setLogEnabled(boolean enable) {
// do nothing
}
@Override
public boolean isPlayable() {
return true;
}
private native String _getVideoCodecInfo();
private native String _getAudioCodecInfo();
public void setOption(int category, String name, String value)
{
_setOption(category, name, value);
}
public void setOption(int category, String name, long value)
{
_setOption(category, name, value);
}
/**播放器的设置是通过C层代码来实现的**/
private native void _setOption(int category, String name, String value);
private native void _setOption(int category, String name, long value);
public Bundle getMediaMeta() {
return _getMediaMeta();
}
private native Bundle _getMediaMeta();
public static String getColorFormatName(int mediaCodecColorFormat) {
return _getColorFormatName(mediaCodecColorFormat);
}
private static native String _getColorFormatName(int mediaCodecColorFormat);
@Override
public void setAudioStreamType(int streamtype) {
// do nothing
}
@Override
public void setKeepInBackground(boolean keepInBackground) {
// do nothing
}
private static native void native_init();
private native void native_setup(Object IjkMediaPlayer_this);
private native void native_finalize();
private native void native_message_loop(Object IjkMediaPlayer_this);
protected void finalize() throws Throwable {
super.finalize();
native_finalize();
}
private static class EventHandler extends Handler {
private final WeakReference<IjkMediaPlayer> mWeakPlayer;
public EventHandler(IjkMediaPlayer mp, Looper looper) {
super(looper);
mWeakPlayer = new WeakReference<IjkMediaPlayer>(mp);
}
@Override
public void handleMessage(Message msg) {
IjkMediaPlayer player = mWeakPlayer.get();
if (player == null || player.mNativeMediaPlayer == 0) {
DebugLog.w(TAG,
"IjkMediaPlayer went away with unhandled events");
return;
}
switch (msg.what) {
case MEDIA_PREPARED:
player.notifyOnPrepared();
return;
case MEDIA_PLAYBACK_COMPLETE:
player.stayAwake(false);
player.notifyOnCompletion();
return;
case MEDIA_BUFFERING_UPDATE:
long bufferPosition = msg.arg1;
if (bufferPosition < 0) {
bufferPosition = 0;
}
long percent = 0;
long duration = player.getDuration();
if (duration > 0) {
percent = bufferPosition * 100 / duration;
}
if (percent >= 100) {
percent = 100;
}
// DebugLog.efmt(TAG, "Buffer (%d%%) %d/%d", percent, bufferPosition, duration);
player.notifyOnBufferingUpdate((int)percent);
return;
case MEDIA_SEEK_COMPLETE:
player.notifyOnSeekComplete();
return;
case MEDIA_SET_VIDEO_SIZE:
player.mVideoWidth = msg.arg1;
player.mVideoHeight = msg.arg2;
player.notifyOnVideoSizeChanged(player.mVideoWidth, player.mVideoHeight,
player.mVideoSarNum, player.mVideoSarDen);
return;
case MEDIA_ERROR:
DebugLog.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
if (!player.notifyOnError(msg.arg1, msg.arg2)) {
player.notifyOnCompletion();
}
player.stayAwake(false);
return;
case MEDIA_INFO:
switch (msg.arg1) {
case MEDIA_INFO_VIDEO_RENDERING_START:
DebugLog.i(TAG, "Info: MEDIA_INFO_VIDEO_RENDERING_START\n");
break;
}
player.notifyOnInfo(msg.arg1, msg.arg2);
// No real default action so far.
return;
case MEDIA_TIMED_TEXT:
// do nothing
break;
case MEDIA_NOP: // interface test message - ignore
break;
case MEDIA_SET_VIDEO_SAR:
player.mVideoSarNum = msg.arg1;
player.mVideoSarDen = msg.arg2;
player.notifyOnVideoSizeChanged(player.mVideoWidth, player.mVideoHeight,
player.mVideoSarNum, player.mVideoSarDen);
break;
default:
DebugLog.e(TAG, "Unknown message type " + msg.what);
}
}
}
/*
* Called from native code when an interesting event happens. This method
* just uses the EventHandler system to post the event back to the main app
* thread. We use a weak reference to the original IjkMediaPlayer object so
* that the native code is safe from the object disappearing from underneath
* it. (This is the cookie passed to native_setup().)
*/
@CalledByNative
private static void postEventFromNative(Object weakThiz, int what,
int arg1, int arg2, Object obj) {
if (weakThiz == null)
return;
@SuppressWarnings("rawtypes")
IjkMediaPlayer mp = (IjkMediaPlayer) ((WeakReference) weakThiz).get();
if (mp == null) {
return;
}
if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
// this acquires the wakelock if needed, and sets the client side
// state
mp.start();
}
if (mp.mEventHandler != null) {
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mp.mEventHandler.sendMessage(m);
}
}
/*
* ControlMessage
*/
private OnControlMessageListener mOnControlMessageListener;
public void setOnControlMessageListener(OnControlMessageListener listener) {
mOnControlMessageListener = listener;
}
public interface OnControlMessageListener {
String onControlResolveSegmentUrl(int segment);
}
/*
* NativeInvoke
*/
private OnNativeInvokeListener mOnNativeInvokeListener;
public void setOnNativeInvokeListener(OnNativeInvokeListener listener) {
mOnNativeInvokeListener = listener;
}
public interface OnNativeInvokeListener {
int ON_CONCAT_RESOLVE_SEGMENT = 0x10000;
int ON_TCP_OPEN = 0x10001;
int ON_HTTP_OPEN = 0x10002;
// int ON_HTTP_RETRY = 0x10003;
int ON_LIVE_RETRY = 0x10004;
String ARG_URL = "url";
String ARG_SEGMENT_INDEX = "segment_index";
String ARG_RETRY_COUNTER = "retry_counter";
/*
* @return true if invoke is handled
* @throws Exception on any error
*/
boolean onNativeInvoke(int what, Bundle args);
}
@CalledByNative
private static boolean onNativeInvoke(Object weakThiz, int what, Bundle args) {
DebugLog.ifmt(TAG, "onNativeInvoke %d", what);
if (weakThiz == null || !(weakThiz instanceof WeakReference<?>))
throw new IllegalStateException("<null weakThiz>.onNativeInvoke()");
@SuppressWarnings("unchecked")
WeakReference<IjkMediaPlayer> weakPlayer = (WeakReference<IjkMediaPlayer>) weakThiz;
IjkMediaPlayer player = weakPlayer.get();
if (player == null)
throw new IllegalStateException("<null weakPlayer>.onNativeInvoke()");
OnNativeInvokeListener listener = player.mOnNativeInvokeListener;
if (listener != null && listener.onNativeInvoke(what, args))
return true;
switch (what) {
case OnNativeInvokeListener.ON_CONCAT_RESOLVE_SEGMENT: {
OnControlMessageListener onControlMessageListener = player.mOnControlMessageListener;
if (onControlMessageListener == null)
return false;
int segmentIndex = args.getInt(OnNativeInvokeListener.ARG_SEGMENT_INDEX, -1);
if (segmentIndex < 0)
throw new InvalidParameterException("onNativeInvoke(invalid segment index)");
String newUrl = onControlMessageListener.onControlResolveSegmentUrl(segmentIndex);
if (newUrl == null)
throw new RuntimeException(new IOException("onNativeInvoke() = <NULL newUrl>"));
args.putString(OnNativeInvokeListener.ARG_URL, newUrl);
return true;
}
default:
return false;
}
}
/*
* MediaCodec select
*/
public interface OnMediaCodecSelectListener {
String onMediaCodecSelect(IMediaPlayer mp, String mimeType, int profile, int level);
}
private OnMediaCodecSelectListener mOnMediaCodecSelectListener;
public void setOnMediaCodecSelectListener(OnMediaCodecSelectListener listener) {
mOnMediaCodecSelectListener = listener;
}
public void resetListeners() {
super.resetListeners();
mOnMediaCodecSelectListener = null;
}
@CalledByNative
private static String onSelectCodec(Object weakThiz, String mimeType, int profile, int level) {
if (weakThiz == null || !(weakThiz instanceof WeakReference<?>))
return null;
@SuppressWarnings("unchecked")
WeakReference<IjkMediaPlayer> weakPlayer = (WeakReference<IjkMediaPlayer>) weakThiz;
IjkMediaPlayer player = weakPlayer.get();
if (player == null)
return null;
OnMediaCodecSelectListener listener = player.mOnMediaCodecSelectListener;
if (listener == null)
listener = DefaultMediaCodecSelector.sInstance;
return listener.onMediaCodecSelect(player, mimeType, profile, level);
}
public static class DefaultMediaCodecSelector implements OnMediaCodecSelectListener {
public static final DefaultMediaCodecSelector sInstance = new DefaultMediaCodecSelector();
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public String onMediaCodecSelect(IMediaPlayer mp, String mimeType, int profile, int level) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
return null;
if (TextUtils.isEmpty(mimeType))
return null;
Log.i(TAG, String.format(Locale.US, "onSelectCodec: mime=%s, profile=%d, level=%d", mimeType, profile, level));
ArrayList<IjkMediaCodecInfo> candidateCodecList = new ArrayList<IjkMediaCodecInfo>();
int numCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < numCodecs; i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
Log.d(TAG, String.format(Locale.US, " found codec: %s", codecInfo.getName()));
if (codecInfo.isEncoder())
continue;
String[] types = codecInfo.getSupportedTypes();
if (types == null)
continue;
for(String type: types) {
if (TextUtils.isEmpty(type))
continue;
Log.d(TAG, String.format(Locale.US, " mime: %s", type));
if (!type.equalsIgnoreCase(mimeType))
continue;
IjkMediaCodecInfo candidate = IjkMediaCodecInfo.setupCandidate(codecInfo, mimeType);
if (candidate == null)
continue;
candidateCodecList.add(candidate);
Log.i(TAG, String.format(Locale.US, "candidate codec: %s rank=%d", codecInfo.getName(), candidate.mRank));
candidate.dumpProfileLevels(mimeType);
}
}
if (candidateCodecList.isEmpty()) {
return null;
}
IjkMediaCodecInfo bestCodec = candidateCodecList.get(0);
for (IjkMediaCodecInfo codec : candidateCodecList) {
if (codec.mRank > bestCodec.mRank) {
bestCodec = codec;
}
}
if (bestCodec.mRank < IjkMediaCodecInfo.RANK_LAST_CHANCE) {
Log.w(TAG, String.format(Locale.US, "unaccetable codec: %s", bestCodec.mCodecInfo.getName()));
return null;
}
Log.i(TAG, String.format(Locale.US, "selected codec: %s rank=%d", bestCodec.mCodecInfo.getName(), bestCodec.mRank));
return bestCodec.mCodecInfo.getName();
}
}
public static native void native_profileBegin(String libName);
public static native void native_profileEnd();
public static native void native_setLogLevel(int level);
}