/* * 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); }