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
+ *
+ *
+ * When path
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;
+ mCompleted = false;
+ _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 headers)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+ if (headers != null && !headers.isEmpty()) {
+ StringBuilder sb = new StringBuilder();
+
+ for(Map.Entry 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());
+ setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "protocol_whitelist", "async,cache,crypto,file,http,https,ijkhttphook,ijkinject,ijklivehook,ijklongurl,ijksegment,ijktcphook,pipe,rtp,tcp,tls,udp,ijkurlhook,data");
+ }
+ }
+
+ 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);
+ }
+
+ public void setDataSourceIjkIOHttp(IIjkIOHttp ijkIOHttp)
+ throws IllegalArgumentException, SecurityException, IllegalStateException {
+ _setDataSourceIjkIOHttp(ijkIOHttp);
+ }
+
+ 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;
+
+ private native void _setDataSourceIjkIOHttp(IIjkIOHttp ijkIOHttp)
+ 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 {
+ mCompleted = false;
+ stayAwake(true);
+ _start();
+ }
+
+ private native void _start() throws IllegalStateException;
+
+ @Override
+ public void stop() throws IllegalStateException {
+ mCompleted = false;
+ 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 trackInfos = new ArrayList();
+ 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);
+ } else if (streamMeta.mType.equalsIgnoreCase(IjkMediaMeta.IJKM_VAL_TYPE__TIMEDTEXT)) {
+ trackInfo.setTrackType(ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT);
+ }
+
+ 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);
+ case ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT:
+ return (int)_getPropertyLong(FFP_PROP_INT64_SELECTED_TIMEDTEXT_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();
+
+ public void setSpeed(float speed) {
+ int selectedTrack = getSelectedTrack(ITrackInfo.MEDIA_TRACK_TYPE_AUDIO);
+
+ if (selectedTrack != -1) {
+ mAudioStream = selectedTrack;
+ }
+
+ if (speed != 1.0f) {
+ deselectTrack(mAudioStream);
+ _setPropertyFloat(FFP_PROP_FLOAT_PLAYBACK_RATE, speed);
+ } else {
+ _setPropertyFloat(FFP_PROP_FLOAT_PLAYBACK_RATE, speed);
+ selectTrack(mAudioStream);
+
+// long pos = getCurrentPosition();
+// stop();
+// try {
+// setDataSource(mDataSource);
+// prepareAsync();
+// start();
+// seekTo(pos);
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+ }
+ }
+
+ public float getSpeed() {
+ 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 getReceivedBytes() {
+ return _getPropertyLong(FFP_PROP_INT64_STATISTIC_COUNT_BYTES, 0);
+ }
+
+ public long getReceivedVideoFrames() {
+ return _getPropertyLong(FFP_PROP_INT64_STATISTIC_COUNT_FRAMES, 0);
+ }
+
+ public long getAudioCachedPackets() {
+ return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_PACKETS, 0);
+ }
+
+ public long getAsyncStatisticBufBackwards() {
+ return _getPropertyLong(FFP_PROP_INT64_ASYNC_STATISTIC_BUF_BACKWARDS, 0);
+ }
+
+ public long getAsyncStatisticBufForwards() {
+ return _getPropertyLong(FFP_PROP_INT64_ASYNC_STATISTIC_BUF_FORWARDS, 0);
+ }
+
+ public long getAsyncStatisticBufCapacity() {
+ return _getPropertyLong(FFP_PROP_INT64_ASYNC_STATISTIC_BUF_CAPACITY, 0);
+ }
+
+ public long getTrafficStatisticByteCount() {
+ return _getPropertyLong(FFP_PROP_INT64_TRAFFIC_STATISTIC_BYTE_COUNT, 0);
+ }
+
+ public long getCacheStatisticPhysicalPos() {
+ return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_PHYSICAL_POS, 0);
+ }
+
+ public long getCacheStatisticBufForwards() {
+ return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_BUF_FORWARDS, 0);
+ }
+
+ public long getCacheStatisticFilePos() {
+ return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_FILE_POS, 0);
+ }
+
+ public long getCacheStatisticCountBytes() {
+ return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_COUNT_BYTES, 0);
+ }
+
+ public long getBitRate() {
+ return _getPropertyLong(FFP_PROP_INT64_BIT_RATE, 0);
+ }
+
+ public long getTcpSpeed() {
+ return _getPropertyLong(FFP_PROP_INT64_TCP_SPEED, 0);
+ }
+
+ public long getSeekLoadDuration() {
+ return _getPropertyLong(FFP_PROP_INT64_LATEST_SEEK_LOAD_DURATION, 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 native void record(String path, int seconds);
+
+ 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);
+ }
+
+ 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
+ }
+
+ public static native long native_active_days(Object context, String key);
+
+ private static native void native_init();
+
+ private native void native_setup(Object ctx, Object IjkMediaPlayer_this, String key);
+// 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 mWeakPlayer;
+
+ public EventHandler(IjkMediaPlayer mp, Looper looper) {
+ super(looper);
+ mWeakPlayer = new WeakReference(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:
+ if (msg.obj == null) {
+ player.notifyOnTimedText(null);
+ } else {
+ IjkTimedText text = new IjkTimedText(new Rect(0, 0, 1, 1), (String)msg.obj);
+ player.notifyOnTimedText(text);
+ }
+
+ return;
+ 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 CTRL_WILL_TCP_OPEN = 0x20001; // NO ARGS
+ int CTRL_DID_TCP_OPEN = 0x20002; // ARG_ERROR, ARG_FAMILIY, ARG_IP, ARG_PORT, ARG_FD
+
+ int CTRL_WILL_HTTP_OPEN = 0x20003; // ARG_URL, ARG_SEGMENT_INDEX, ARG_RETRY_COUNTER
+ int CTRL_WILL_LIVE_OPEN = 0x20005; // ARG_URL, ARG_RETRY_COUNTER
+ int CTRL_WILL_CONCAT_RESOLVE_SEGMENT = 0x20007; // ARG_URL, ARG_SEGMENT_INDEX, ARG_RETRY_COUNTER
+
+ int EVENT_WILL_HTTP_OPEN = 0x1; // ARG_URL
+ int EVENT_DID_HTTP_OPEN = 0x2; // ARG_URL, ARG_ERROR, ARG_HTTP_CODE
+ int EVENT_WILL_HTTP_SEEK = 0x3; // ARG_URL, ARG_OFFSET
+ int EVENT_DID_HTTP_SEEK = 0x4; // ARG_URL, ARG_OFFSET, ARG_ERROR, ARG_HTTP_CODE
+
+ String ARG_URL = "url";
+ String ARG_SEGMENT_INDEX = "segment_index";
+ String ARG_RETRY_COUNTER = "retry_counter";
+
+ String ARG_ERROR = "error";
+ String ARG_FAMILIY = "family";
+ String ARG_IP = "ip";
+ String ARG_PORT = "port";
+ String ARG_FD = "fd";
+
+ String ARG_OFFSET = "offset";
+ String ARG_HTTP_CODE = "http_code";
+
+ /*
+ * @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(".onNativeInvoke()");
+
+ @SuppressWarnings("unchecked")
+ WeakReference weakPlayer = (WeakReference) weakThiz;
+ IjkMediaPlayer player = weakPlayer.get();
+
+ if (player == null)
+ throw new IllegalStateException(".onNativeInvoke()");
+
+ OnNativeInvokeListener listener = player.mOnNativeInvokeListener;
+
+ if (listener != null && listener.onNativeInvoke(what, args))
+ return true;
+
+ switch (what) {
+ case OnNativeInvokeListener.CTRL_WILL_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() = "));
+
+ 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 weakPlayer = (WeakReference) 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 candidateCodecList = new ArrayList();
+ 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);
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkTimedText.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkTimedText.java
new file mode 100644
index 0000000..0d11ae4
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkTimedText.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 Zheng Yuan
+ *
+ * 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.graphics.Rect;
+import java.lang.String;
+
+public final class IjkTimedText {
+
+ private Rect mTextBounds = null;
+ private String mTextChars = null;
+
+ public IjkTimedText(Rect bounds, String text) {
+ mTextBounds = bounds;
+ mTextChars = text;
+ }
+
+ public Rect getBounds() {
+ return mTextBounds;
+ }
+
+ public String getText() {
+ return mTextChars;
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/MediaInfo.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/MediaInfo.java
new file mode 100644
index 0000000..6cd5004
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/MediaInfo.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013-2014 Zhang Rui
+ *
+ * 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;
+
+public class MediaInfo {
+ public String mMediaPlayerName;
+
+ public String mVideoDecoder;
+ public String mVideoDecoderImpl;
+
+ public String mAudioDecoder;
+ public String mAudioDecoderImpl;
+
+ public IjkMediaMeta mMeta;
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/MediaPlayerProxy.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/MediaPlayerProxy.java
new file mode 100644
index 0000000..f33ce0b
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/MediaPlayerProxy.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.TargetApi;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.Map;
+
+import tv.danmaku.ijk.media.player.misc.IMediaDataSource;
+import tv.danmaku.ijk.media.player.misc.ITrackInfo;
+
+public class MediaPlayerProxy implements IMediaPlayer {
+ protected final IMediaPlayer mBackEndMediaPlayer;
+
+ public MediaPlayerProxy(IMediaPlayer backEndMediaPlayer) {
+ mBackEndMediaPlayer = backEndMediaPlayer;
+ }
+
+ public IMediaPlayer getInternalMediaPlayer() {
+ return mBackEndMediaPlayer;
+ }
+
+ @Override
+ public void setDisplay(SurfaceHolder sh) {
+ mBackEndMediaPlayer.setDisplay(sh);
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void setSurface(Surface surface) {
+ mBackEndMediaPlayer.setSurface(surface);
+ }
+
+ @Override
+ public void setDataSource(Context context, Uri uri)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+ mBackEndMediaPlayer.setDataSource(context, uri);
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void setDataSource(Context context, Uri uri, Map headers)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+ mBackEndMediaPlayer.setDataSource(context, uri, headers);
+ }
+
+ @Override
+ public void setDataSource(FileDescriptor fd)
+ throws IOException, IllegalArgumentException, IllegalStateException {
+ mBackEndMediaPlayer.setDataSource(fd);
+ }
+
+ @Override
+ public void setDataSource(String path) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+ mBackEndMediaPlayer.setDataSource(path);
+ }
+
+ @Override
+ public void setDataSource(IMediaDataSource mediaDataSource) {
+ mBackEndMediaPlayer.setDataSource(mediaDataSource);
+ }
+
+ @Override
+ public String getDataSource() {
+ return mBackEndMediaPlayer.getDataSource();
+ }
+
+ @Override
+ public void prepareAsync() throws IllegalStateException {
+ mBackEndMediaPlayer.prepareAsync();
+ }
+
+ @Override
+ public void start() throws IllegalStateException {
+ mBackEndMediaPlayer.start();
+ }
+
+ @Override
+ public void stop() throws IllegalStateException {
+ mBackEndMediaPlayer.stop();
+ }
+
+ @Override
+ public void pause() throws IllegalStateException {
+ mBackEndMediaPlayer.pause();
+ }
+
+ @Override
+ public void setScreenOnWhilePlaying(boolean screenOn) {
+ mBackEndMediaPlayer.setScreenOnWhilePlaying(screenOn);
+ }
+
+ @Override
+ public int getVideoWidth() {
+ return mBackEndMediaPlayer.getVideoWidth();
+ }
+
+ @Override
+ public int getVideoHeight() {
+ return mBackEndMediaPlayer.getVideoHeight();
+ }
+
+ @Override
+ public boolean isPlaying() {
+ return mBackEndMediaPlayer.isPlaying();
+ }
+
+ @Override
+ public void seekTo(long msec) throws IllegalStateException {
+ mBackEndMediaPlayer.seekTo(msec);
+ }
+
+ @Override
+ public long getCurrentPosition() {
+ return mBackEndMediaPlayer.getCurrentPosition();
+ }
+
+ @Override
+ public long getDuration() {
+ return mBackEndMediaPlayer.getDuration();
+ }
+
+ @Override
+ public void release() {
+ mBackEndMediaPlayer.release();
+ }
+
+ @Override
+ public void reset() {
+ mBackEndMediaPlayer.reset();
+ }
+
+ @Override
+ public void record(String path, int seconds) {
+
+ }
+
+ @Override
+ public void setVolume(float leftVolume, float rightVolume) {
+ mBackEndMediaPlayer.setVolume(leftVolume, rightVolume);
+ }
+
+ @Override
+ public int getAudioSessionId() {
+ return mBackEndMediaPlayer.getAudioSessionId();
+ }
+
+ @Override
+ public MediaInfo getMediaInfo() {
+ return mBackEndMediaPlayer.getMediaInfo();
+ }
+
+ @Override
+ public void setLogEnabled(boolean enable) {
+
+ }
+
+ @Override
+ public boolean isPlayable() {
+ return false;
+ }
+
+ @Override
+ public void setOnPreparedListener(OnPreparedListener listener) {
+ if (listener != null) {
+ final OnPreparedListener finalListener = listener;
+ mBackEndMediaPlayer.setOnPreparedListener(new OnPreparedListener() {
+ @Override
+ public void onPrepared(IMediaPlayer mp) {
+ finalListener.onPrepared(MediaPlayerProxy.this);
+ }
+ });
+ } else {
+ mBackEndMediaPlayer.setOnPreparedListener(null);
+ }
+ }
+
+ @Override
+ public void setOnCompletionListener(OnCompletionListener listener) {
+ if (listener != null) {
+ final OnCompletionListener finalListener = listener;
+ mBackEndMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
+ @Override
+ public void onCompletion(IMediaPlayer mp) {
+ finalListener.onCompletion(MediaPlayerProxy.this);
+ }
+ });
+ } else {
+ mBackEndMediaPlayer.setOnCompletionListener(null);
+ }
+ }
+
+ @Override
+ public void setOnBufferingUpdateListener(OnBufferingUpdateListener listener) {
+ if (listener != null) {
+ final OnBufferingUpdateListener finalListener = listener;
+ mBackEndMediaPlayer.setOnBufferingUpdateListener(new OnBufferingUpdateListener() {
+ @Override
+ public void onBufferingUpdate(IMediaPlayer mp, int percent) {
+ finalListener.onBufferingUpdate(MediaPlayerProxy.this, percent);
+ }
+ });
+ } else {
+ mBackEndMediaPlayer.setOnBufferingUpdateListener(null);
+ }
+ }
+
+ @Override
+ public void setOnSeekCompleteListener(OnSeekCompleteListener listener) {
+ if (listener != null) {
+ final OnSeekCompleteListener finalListener = listener;
+ mBackEndMediaPlayer.setOnSeekCompleteListener(new OnSeekCompleteListener() {
+ @Override
+ public void onSeekComplete(IMediaPlayer mp) {
+ finalListener.onSeekComplete(MediaPlayerProxy.this);
+ }
+ });
+ } else {
+ mBackEndMediaPlayer.setOnSeekCompleteListener(null);
+ }
+ }
+
+ @Override
+ public void setOnVideoSizeChangedListener(OnVideoSizeChangedListener listener) {
+ if (listener != null) {
+ final OnVideoSizeChangedListener finalListener = listener;
+ mBackEndMediaPlayer.setOnVideoSizeChangedListener(new OnVideoSizeChangedListener() {
+ @Override
+ public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, int sar_num, int sar_den) {
+ finalListener.onVideoSizeChanged(MediaPlayerProxy.this, width, height, sar_num, sar_den);
+ }
+ });
+ } else {
+ mBackEndMediaPlayer.setOnVideoSizeChangedListener(null);
+ }
+ }
+
+ @Override
+ public void setOnErrorListener(OnErrorListener listener) {
+ if (listener != null) {
+ final OnErrorListener finalListener = listener;
+ mBackEndMediaPlayer.setOnErrorListener(new OnErrorListener() {
+ @Override
+ public boolean onError(IMediaPlayer mp, int what, int extra) {
+ return finalListener.onError(MediaPlayerProxy.this, what, extra);
+ }
+ });
+ } else {
+ mBackEndMediaPlayer.setOnErrorListener(null);
+ }
+ }
+
+ @Override
+ public void setOnInfoListener(OnInfoListener listener) {
+ if (listener != null) {
+ final OnInfoListener finalListener = listener;
+ mBackEndMediaPlayer.setOnInfoListener(new OnInfoListener() {
+ @Override
+ public boolean onInfo(IMediaPlayer mp, int what, int extra) {
+ return finalListener.onInfo(MediaPlayerProxy.this, what, extra);
+ }
+ });
+ } else {
+ mBackEndMediaPlayer.setOnInfoListener(null);
+ }
+ }
+
+ @Override
+ public void setOnTimedTextListener(OnTimedTextListener listener) {
+ if (listener != null) {
+ final OnTimedTextListener finalListener = listener;
+ mBackEndMediaPlayer.setOnTimedTextListener(new OnTimedTextListener() {
+ @Override
+ public void onTimedText(IMediaPlayer mp, IjkTimedText text) {
+ finalListener.onTimedText(MediaPlayerProxy.this, text);
+ }
+ });
+ } else {
+ mBackEndMediaPlayer.setOnTimedTextListener(null);
+ }
+ }
+
+ @Override
+ public void setAudioStreamType(int streamtype) {
+ mBackEndMediaPlayer.setAudioStreamType(streamtype);
+ }
+
+ @Override
+ public void setKeepInBackground(boolean keepInBackground) {
+ mBackEndMediaPlayer.setKeepInBackground(keepInBackground);
+ }
+
+ @Override
+ public int getVideoSarNum() {
+ return mBackEndMediaPlayer.getVideoSarNum();
+ }
+
+ @Override
+ public int getVideoSarDen() {
+ return mBackEndMediaPlayer.getVideoSarDen();
+ }
+
+ @Override
+ public void setWakeMode(Context context, int mode) {
+ mBackEndMediaPlayer.setWakeMode(context, mode);
+ }
+
+ @Override
+ public ITrackInfo[] getTrackInfo() {
+ return mBackEndMediaPlayer.getTrackInfo();
+ }
+
+ @Override
+ public void setLooping(boolean looping) {
+ mBackEndMediaPlayer.setLooping(looping);
+ }
+
+ @Override
+ public boolean isLooping() {
+ return mBackEndMediaPlayer.isLooping();
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/TextureMediaPlayer.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/TextureMediaPlayer.java
new file mode 100644
index 0000000..22a269e
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/TextureMediaPlayer.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.TargetApi;
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class TextureMediaPlayer extends MediaPlayerProxy implements IMediaPlayer, ISurfaceTextureHolder {
+ private SurfaceTexture mSurfaceTexture;
+ private ISurfaceTextureHost mSurfaceTextureHost;
+
+ public TextureMediaPlayer(IMediaPlayer backEndMediaPlayer) {
+ super(backEndMediaPlayer);
+ }
+
+ public void releaseSurfaceTexture() {
+ if (mSurfaceTexture != null) {
+ if (mSurfaceTextureHost != null) {
+ mSurfaceTextureHost.releaseSurfaceTexture(mSurfaceTexture);
+ } else {
+ mSurfaceTexture.release();
+ }
+ mSurfaceTexture = null;
+ }
+ }
+
+ //--------------------
+ // IMediaPlayer
+ //--------------------
+ @Override
+ public void reset() {
+ super.reset();
+ releaseSurfaceTexture();
+ }
+
+ @Override
+ public void release() {
+ super.release();
+ releaseSurfaceTexture();
+ }
+
+ @Override
+ public void setDisplay(SurfaceHolder sh) {
+ if (mSurfaceTexture == null)
+ super.setDisplay(sh);
+ }
+
+ @Override
+ public void setSurface(Surface surface) {
+ if (mSurfaceTexture == null)
+ super.setSurface(surface);
+ }
+
+ //--------------------
+ // ISurfaceTextureHolder
+ //--------------------
+
+ @Override
+ public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
+ if (mSurfaceTexture == surfaceTexture)
+ return;
+
+ releaseSurfaceTexture();
+ mSurfaceTexture = surfaceTexture;
+ if (surfaceTexture == null) {
+ super.setSurface(null);
+ } else {
+ super.setSurface(new Surface(surfaceTexture));
+ }
+ }
+
+ @Override
+ public SurfaceTexture getSurfaceTexture() {
+ return mSurfaceTexture;
+ }
+
+ @Override
+ public void setSurfaceTextureHost(ISurfaceTextureHost surfaceTextureHost) {
+ mSurfaceTextureHost = surfaceTextureHost;
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/TxtOverlayAdd.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/TxtOverlayAdd.java
new file mode 100644
index 0000000..158eb20
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/TxtOverlayAdd.java
@@ -0,0 +1,14 @@
+package tv.danmaku.ijk.media.player;
+
+import tv.danmaku.ijk.media.player.annotations.AccessedByNative;
+
+/**
+ * Created by jiaozebo on 2017/2/23.
+ */
+
+public class TxtOverlayAdd {
+
+ @AccessedByNative
+ private long mNativeMediaPlayer;
+
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/annotations/AccessedByNative.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/annotations/AccessedByNative.java
new file mode 100644
index 0000000..c613660
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/annotations/AccessedByNative.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013-2014 Zhang Rui
+ *
+ * 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * is used by the JNI generator to create the necessary JNI
+ * bindings and expose this method to native code.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.CLASS)
+public @interface AccessedByNative {
+}
\ No newline at end of file
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/annotations/CalledByNative.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/annotations/CalledByNative.java
new file mode 100644
index 0000000..2a2caf3
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/annotations/CalledByNative.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013-2014 Zhang Rui
+ *
+ * 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * is used by the JNI generator to create the necessary JNI
+ * bindings and expose this method to native code.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface CalledByNative {
+ /*
+ * If present, tells which inner class the method belongs to.
+ */
+ String value() default "";
+}
\ No newline at end of file
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/exceptions/IjkMediaException.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/exceptions/IjkMediaException.java
new file mode 100644
index 0000000..dbd1add
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/exceptions/IjkMediaException.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2013-2014 Zhang Rui
+ *
+ * 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.exceptions;
+
+public class IjkMediaException extends Exception {
+ private static final long serialVersionUID = 7234796519009099506L;
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/ffmpeg/FFmpegApi.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/ffmpeg/FFmpegApi.java
new file mode 100644
index 0000000..e13ba1b
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/ffmpeg/FFmpegApi.java
@@ -0,0 +1,5 @@
+package tv.danmaku.ijk.media.player.ffmpeg;
+
+public class FFmpegApi {
+ public static native String av_base64_encode(byte in[]);
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/AndroidMediaFormat.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/AndroidMediaFormat.java
new file mode 100644
index 0000000..9338a24
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/AndroidMediaFormat.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.misc;
+
+import android.annotation.TargetApi;
+import android.media.MediaFormat;
+import android.os.Build;
+
+public class AndroidMediaFormat implements IMediaFormat {
+ private final MediaFormat mMediaFormat;
+
+ public AndroidMediaFormat(MediaFormat mediaFormat) {
+ mMediaFormat = mediaFormat;
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ @Override
+ public int getInteger(String name) {
+ if (mMediaFormat == null)
+ return 0;
+
+ return mMediaFormat.getInteger(name);
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ @Override
+ public String getString(String name) {
+ if (mMediaFormat == null)
+ return null;
+
+ return mMediaFormat.getString(name);
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder(128);
+ out.append(getClass().getName());
+ out.append('{');
+ if (mMediaFormat != null) {
+ out.append(mMediaFormat.toString());
+ } else {
+ out.append("null");
+ }
+ out.append('}');
+ return out.toString();
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/AndroidTrackInfo.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/AndroidTrackInfo.java
new file mode 100644
index 0000000..33e7556
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/AndroidTrackInfo.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.misc;
+
+import android.annotation.TargetApi;
+import android.media.MediaFormat;
+import android.media.MediaPlayer;
+import android.os.Build;
+
+public class AndroidTrackInfo implements ITrackInfo {
+ private final MediaPlayer.TrackInfo mTrackInfo;
+
+ public static AndroidTrackInfo[] fromMediaPlayer(MediaPlayer mp) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
+ return fromTrackInfo(mp.getTrackInfo());
+
+ return null;
+ }
+
+ private static AndroidTrackInfo[] fromTrackInfo(MediaPlayer.TrackInfo[] trackInfos) {
+ if (trackInfos == null)
+ return null;
+
+ AndroidTrackInfo androidTrackInfo[] = new AndroidTrackInfo[trackInfos.length];
+ for (int i = 0; i < trackInfos.length; ++i) {
+ androidTrackInfo[i] = new AndroidTrackInfo(trackInfos[i]);
+ }
+
+ return androidTrackInfo;
+ }
+
+ private AndroidTrackInfo(MediaPlayer.TrackInfo trackInfo) {
+ mTrackInfo = trackInfo;
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ @Override
+ public IMediaFormat getFormat() {
+ if (mTrackInfo == null)
+ return null;
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
+ return null;
+
+ MediaFormat mediaFormat = mTrackInfo.getFormat();
+ if (mediaFormat == null)
+ return null;
+
+ return new AndroidMediaFormat(mediaFormat);
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ @Override
+ public String getLanguage() {
+ if (mTrackInfo == null)
+ return "und";
+
+ return mTrackInfo.getLanguage();
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ @Override
+ public int getTrackType() {
+ if (mTrackInfo == null)
+ return MEDIA_TRACK_TYPE_UNKNOWN;
+
+ return mTrackInfo.getTrackType();
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder(128);
+ out.append(getClass().getSimpleName());
+ out.append('{');
+ if (mTrackInfo != null) {
+ out.append(mTrackInfo.toString());
+ } else {
+ out.append("null");
+ }
+ out.append('}');
+ return out.toString();
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ @Override
+ public String getInfoInline() {
+ if (mTrackInfo != null) {
+ return mTrackInfo.toString();
+ } else {
+ return "null";
+ }
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IIjkIOHttp.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IIjkIOHttp.java
new file mode 100644
index 0000000..914fc35
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IIjkIOHttp.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 Raymond Zheng
+ *
+ * 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.misc;
+
+import java.io.IOException;
+
+@SuppressWarnings("RedundantThrows")
+public interface IIjkIOHttp {
+ int open() throws IOException;
+ int read(byte[] buffer, int size) throws IOException;
+ long seek(long offset, int whence) throws IOException;
+ int close() throws IOException;
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IMediaDataSource.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IMediaDataSource.java
new file mode 100644
index 0000000..a298aa9
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IMediaDataSource.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.misc;
+
+import java.io.IOException;
+
+@SuppressWarnings("RedundantThrows")
+public interface IMediaDataSource {
+ int readAt(long position, byte[] buffer, int offset, int size) throws IOException;
+
+ long getSize() throws IOException;
+
+ void close() throws IOException;
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IMediaFormat.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IMediaFormat.java
new file mode 100644
index 0000000..b1cd4db
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IMediaFormat.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.misc;
+
+public interface IMediaFormat {
+ // Common keys
+ String KEY_MIME = "mime";
+
+ // Video Keys
+ String KEY_WIDTH = "width";
+ String KEY_HEIGHT = "height";
+
+ String getString(String name);
+
+ int getInteger(String name);
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/ITrackInfo.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/ITrackInfo.java
new file mode 100644
index 0000000..a9c5101
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/ITrackInfo.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.misc;
+
+public interface ITrackInfo {
+ int MEDIA_TRACK_TYPE_AUDIO = 2;
+ int MEDIA_TRACK_TYPE_METADATA = 5;
+ int MEDIA_TRACK_TYPE_SUBTITLE = 4;
+ int MEDIA_TRACK_TYPE_TIMEDTEXT = 3;
+ int MEDIA_TRACK_TYPE_UNKNOWN = 0;
+ int MEDIA_TRACK_TYPE_VIDEO = 1;
+
+ IMediaFormat getFormat();
+
+ String getLanguage();
+
+ int getTrackType();
+
+ String getInfoInline();
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IjkMediaFormat.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IjkMediaFormat.java
new file mode 100644
index 0000000..e878c8f
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IjkMediaFormat.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.misc;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.text.TextUtils;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import tv.danmaku.ijk.media.player.IjkMediaMeta;
+
+public class IjkMediaFormat implements IMediaFormat {
+ // Common
+ public static final String KEY_IJK_CODEC_LONG_NAME_UI = "ijk-codec-long-name-ui";
+ public static final String KEY_IJK_CODEC_NAME_UI = "ijk-codec-name-ui";
+ public static final String KEY_IJK_BIT_RATE_UI = "ijk-bit-rate-ui";
+
+ // Video
+ public static final String KEY_IJK_CODEC_PROFILE_LEVEL_UI = "ijk-profile-level-ui";
+ public static final String KEY_IJK_CODEC_PIXEL_FORMAT_UI = "ijk-pixel-format-ui";
+ public static final String KEY_IJK_RESOLUTION_UI = "ijk-resolution-ui";
+ public static final String KEY_IJK_FRAME_RATE_UI = "ijk-frame-rate-ui";
+
+ // Audio
+ public static final String KEY_IJK_SAMPLE_RATE_UI = "ijk-sample-rate-ui";
+ public static final String KEY_IJK_CHANNEL_UI = "ijk-channel-ui";
+
+ // Codec
+ public static final String CODEC_NAME_H264 = "h264";
+
+ public final IjkMediaMeta.IjkStreamMeta mMediaFormat;
+
+ public IjkMediaFormat(IjkMediaMeta.IjkStreamMeta streamMeta) {
+ mMediaFormat = streamMeta;
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ @Override
+ public int getInteger(String name) {
+ if (mMediaFormat == null)
+ return 0;
+
+ return mMediaFormat.getInt(name);
+ }
+
+ @Override
+ public String getString(String name) {
+ if (mMediaFormat == null)
+ return null;
+
+ if (sFormatterMap.containsKey(name)) {
+ Formatter formatter = sFormatterMap.get(name);
+ return formatter.format(this);
+ }
+
+ return mMediaFormat.getString(name);
+ }
+
+ //-------------------------
+ // Formatter
+ //-------------------------
+
+ private static abstract class Formatter {
+ public String format(IjkMediaFormat mediaFormat) {
+ String value = doFormat(mediaFormat);
+ if (TextUtils.isEmpty(value))
+ return getDefaultString();
+ return value;
+ }
+
+ protected abstract String doFormat(IjkMediaFormat mediaFormat);
+
+ @SuppressWarnings("SameReturnValue")
+ protected String getDefaultString() {
+ return "N/A";
+ }
+ }
+
+ private static final Map sFormatterMap = new HashMap();
+
+ {
+ sFormatterMap.put(KEY_IJK_CODEC_LONG_NAME_UI, new Formatter() {
+ @Override
+ public String doFormat(IjkMediaFormat mediaFormat) {
+ return mMediaFormat.getString(IjkMediaMeta.IJKM_KEY_CODEC_LONG_NAME);
+ }
+ });
+ sFormatterMap.put(KEY_IJK_CODEC_NAME_UI, new Formatter() {
+ @Override
+ public String doFormat(IjkMediaFormat mediaFormat) {
+ return mMediaFormat.getString(IjkMediaMeta.IJKM_KEY_CODEC_NAME);
+ }
+ });
+ sFormatterMap.put(KEY_IJK_BIT_RATE_UI, new Formatter() {
+ @Override
+ protected String doFormat(IjkMediaFormat mediaFormat) {
+ int bitRate = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_BITRATE);
+ if (bitRate <= 0) {
+ return null;
+ } else if (bitRate < 1000) {
+ return String.format(Locale.US, "%d bit/s", bitRate);
+ } else {
+ return String.format(Locale.US, "%d kb/s", bitRate / 1000);
+ }
+ }
+ });
+ sFormatterMap.put(KEY_IJK_CODEC_PROFILE_LEVEL_UI, new Formatter() {
+ @Override
+ protected String doFormat(IjkMediaFormat mediaFormat) {
+ int profileIndex = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_CODEC_PROFILE_ID);
+ String profile;
+ switch (profileIndex) {
+ case IjkMediaMeta.FF_PROFILE_H264_BASELINE:
+ profile = "Baseline";
+ break;
+ case IjkMediaMeta.FF_PROFILE_H264_CONSTRAINED_BASELINE:
+ profile = "Constrained Baseline";
+ break;
+ case IjkMediaMeta.FF_PROFILE_H264_MAIN:
+ profile = "Main";
+ break;
+ case IjkMediaMeta.FF_PROFILE_H264_EXTENDED:
+ profile = "Extended";
+ break;
+ case IjkMediaMeta.FF_PROFILE_H264_HIGH:
+ profile = "High";
+ break;
+ case IjkMediaMeta.FF_PROFILE_H264_HIGH_10:
+ profile = "High 10";
+ break;
+ case IjkMediaMeta.FF_PROFILE_H264_HIGH_10_INTRA:
+ profile = "High 10 Intra";
+ break;
+ case IjkMediaMeta.FF_PROFILE_H264_HIGH_422:
+ profile = "High 4:2:2";
+ break;
+ case IjkMediaMeta.FF_PROFILE_H264_HIGH_422_INTRA:
+ profile = "High 4:2:2 Intra";
+ break;
+ case IjkMediaMeta.FF_PROFILE_H264_HIGH_444:
+ profile = "High 4:4:4";
+ break;
+ case IjkMediaMeta.FF_PROFILE_H264_HIGH_444_PREDICTIVE:
+ profile = "High 4:4:4 Predictive";
+ break;
+ case IjkMediaMeta.FF_PROFILE_H264_HIGH_444_INTRA:
+ profile = "High 4:4:4 Intra";
+ break;
+ case IjkMediaMeta.FF_PROFILE_H264_CAVLC_444:
+ profile = "CAVLC 4:4:4";
+ break;
+ default:
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(profile);
+
+ String codecName = mediaFormat.getString(IjkMediaMeta.IJKM_KEY_CODEC_NAME);
+ if (!TextUtils.isEmpty(codecName) && codecName.equalsIgnoreCase(CODEC_NAME_H264)) {
+ int level = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_CODEC_LEVEL);
+ if (level < 10)
+ return sb.toString();
+
+ sb.append(" Profile Level ");
+ sb.append((level / 10) % 10);
+ if ((level % 10) != 0) {
+ sb.append(".");
+ sb.append(level % 10);
+ }
+ }
+
+ return sb.toString();
+ }
+ });
+ sFormatterMap.put(KEY_IJK_CODEC_PIXEL_FORMAT_UI, new Formatter() {
+ @Override
+ protected String doFormat(IjkMediaFormat mediaFormat) {
+ return mediaFormat.getString(IjkMediaMeta.IJKM_KEY_CODEC_PIXEL_FORMAT);
+ }
+ });
+ sFormatterMap.put(KEY_IJK_RESOLUTION_UI, new Formatter() {
+ @Override
+ protected String doFormat(IjkMediaFormat mediaFormat) {
+ int width = mediaFormat.getInteger(KEY_WIDTH);
+ int height = mediaFormat.getInteger(KEY_HEIGHT);
+ int sarNum = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_SAR_NUM);
+ int sarDen = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_SAR_DEN);
+
+ if (width <= 0 || height <= 0) {
+ return null;
+ } else if (sarNum <= 0 || sarDen <= 0) {
+ return String.format(Locale.US, "%d x %d", width, height);
+ } else {
+ return String.format(Locale.US, "%d x %d [SAR %d:%d]", width,
+ height, sarNum, sarDen);
+ }
+ }
+ });
+ sFormatterMap.put(KEY_IJK_FRAME_RATE_UI, new Formatter() {
+ @Override
+ protected String doFormat(IjkMediaFormat mediaFormat) {
+ int fpsNum = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_FPS_NUM);
+ int fpsDen = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_FPS_DEN);
+ if (fpsNum <= 0 || fpsDen <= 0) {
+ return null;
+ } else {
+ return String.valueOf(((float) (fpsNum)) / fpsDen);
+ }
+ }
+ });
+ sFormatterMap.put(KEY_IJK_SAMPLE_RATE_UI, new Formatter() {
+ @Override
+ protected String doFormat(IjkMediaFormat mediaFormat) {
+ int sampleRate = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_SAMPLE_RATE);
+ if (sampleRate <= 0) {
+ return null;
+ } else {
+ return String.format(Locale.US, "%d Hz", sampleRate);
+ }
+ }
+ });
+ sFormatterMap.put(KEY_IJK_CHANNEL_UI, new Formatter() {
+ @Override
+ protected String doFormat(IjkMediaFormat mediaFormat) {
+ int channelLayout = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_CHANNEL_LAYOUT);
+ if (channelLayout <= 0) {
+ return null;
+ } else {
+ if (channelLayout == IjkMediaMeta.AV_CH_LAYOUT_MONO) {
+ return "mono";
+ } else if (channelLayout == IjkMediaMeta.AV_CH_LAYOUT_STEREO) {
+ return "stereo";
+ } else {
+ return String.format(Locale.US, "%x", channelLayout);
+ }
+ }
+ }
+ });
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IjkTrackInfo.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IjkTrackInfo.java
new file mode 100644
index 0000000..52572c5
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/misc/IjkTrackInfo.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.misc;
+
+import android.text.TextUtils;
+
+import tv.danmaku.ijk.media.player.IjkMediaMeta;
+
+public class IjkTrackInfo implements ITrackInfo {
+ private int mTrackType = MEDIA_TRACK_TYPE_UNKNOWN;
+ private IjkMediaMeta.IjkStreamMeta mStreamMeta;
+
+ public IjkTrackInfo(IjkMediaMeta.IjkStreamMeta streamMeta) {
+ mStreamMeta = streamMeta;
+ }
+
+ public void setMediaMeta(IjkMediaMeta.IjkStreamMeta streamMeta) {
+ mStreamMeta = streamMeta;
+ }
+
+ @Override
+ public IMediaFormat getFormat() {
+ return new IjkMediaFormat(mStreamMeta);
+ }
+
+ @Override
+ public String getLanguage() {
+ if (mStreamMeta == null || TextUtils.isEmpty(mStreamMeta.mLanguage))
+ return "und";
+
+ return mStreamMeta.mLanguage;
+ }
+
+ @Override
+ public int getTrackType() {
+ return mTrackType;
+ }
+
+ public void setTrackType(int trackType) {
+ mTrackType = trackType;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + '{' + getInfoInline() + "}";
+ }
+
+ @Override
+ public String getInfoInline() {
+ StringBuilder out = new StringBuilder(128);
+ switch (mTrackType) {
+ case MEDIA_TRACK_TYPE_VIDEO:
+ out.append("VIDEO");
+ out.append(", ");
+ out.append(mStreamMeta.getCodecShortNameInline());
+ out.append(", ");
+ out.append(mStreamMeta.getBitrateInline());
+ out.append(", ");
+ out.append(mStreamMeta.getResolutionInline());
+ break;
+ case MEDIA_TRACK_TYPE_AUDIO:
+ out.append("AUDIO");
+ out.append(", ");
+ out.append(mStreamMeta.getCodecShortNameInline());
+ out.append(", ");
+ out.append(mStreamMeta.getBitrateInline());
+ out.append(", ");
+ out.append(mStreamMeta.getSampleRateInline());
+ break;
+ case MEDIA_TRACK_TYPE_TIMEDTEXT:
+ out.append("TIMEDTEXT");
+ out.append(", ");
+ out.append(mStreamMeta.mLanguage);
+ break;
+ case MEDIA_TRACK_TYPE_SUBTITLE:
+ out.append("SUBTITLE");
+ break;
+ default:
+ out.append("UNKNOWN");
+ break;
+ }
+ return out.toString();
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/pragma/DebugLog.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/pragma/DebugLog.java
new file mode 100644
index 0000000..7ccd89d
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/pragma/DebugLog.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2013 Zhang Rui
+ *
+ * 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.pragma;
+
+import java.util.Locale;
+
+
+import android.util.Log;
+
+@SuppressWarnings({"SameParameterValue", "WeakerAccess"})
+public class DebugLog {
+ public static final boolean ENABLE_ERROR = Pragma.ENABLE_VERBOSE;
+ public static final boolean ENABLE_INFO = Pragma.ENABLE_VERBOSE;
+ public static final boolean ENABLE_WARN = Pragma.ENABLE_VERBOSE;
+ public static final boolean ENABLE_DEBUG = Pragma.ENABLE_VERBOSE;
+ public static final boolean ENABLE_VERBOSE = Pragma.ENABLE_VERBOSE;
+
+ public static void e(String tag, String msg) {
+ if (ENABLE_ERROR) {
+ Log.e(tag, msg);
+ }
+ }
+
+ public static void e(String tag, String msg, Throwable tr) {
+ if (ENABLE_ERROR) {
+ Log.e(tag, msg, tr);
+ }
+ }
+
+ public static void efmt(String tag, String fmt, Object... args) {
+ if (ENABLE_ERROR) {
+ String msg = String.format(Locale.US, fmt, args);
+ Log.e(tag, msg);
+ }
+ }
+
+ public static void i(String tag, String msg) {
+ if (ENABLE_INFO) {
+ Log.i(tag, msg);
+ }
+ }
+
+ public static void i(String tag, String msg, Throwable tr) {
+ if (ENABLE_INFO) {
+ Log.i(tag, msg, tr);
+ }
+ }
+
+ public static void ifmt(String tag, String fmt, Object... args) {
+ if (ENABLE_INFO) {
+ String msg = String.format(Locale.US, fmt, args);
+ Log.i(tag, msg);
+ }
+ }
+
+ public static void w(String tag, String msg) {
+ if (ENABLE_WARN) {
+ Log.w(tag, msg);
+ }
+ }
+
+ public static void w(String tag, String msg, Throwable tr) {
+ if (ENABLE_WARN) {
+ Log.w(tag, msg, tr);
+ }
+ }
+
+ public static void wfmt(String tag, String fmt, Object... args) {
+ if (ENABLE_WARN) {
+ String msg = String.format(Locale.US, fmt, args);
+ Log.w(tag, msg);
+ }
+ }
+
+ public static void d(String tag, String msg) {
+ if (ENABLE_DEBUG) {
+ Log.d(tag, msg);
+ }
+ }
+
+ public static void d(String tag, String msg, Throwable tr) {
+ if (ENABLE_DEBUG) {
+ Log.d(tag, msg, tr);
+ }
+ }
+
+ public static void dfmt(String tag, String fmt, Object... args) {
+ if (ENABLE_DEBUG) {
+ String msg = String.format(Locale.US, fmt, args);
+ Log.d(tag, msg);
+ }
+ }
+
+ public static void v(String tag, String msg) {
+ if (ENABLE_VERBOSE) {
+ Log.v(tag, msg);
+ }
+ }
+
+ public static void v(String tag, String msg, Throwable tr) {
+ if (ENABLE_VERBOSE) {
+ Log.v(tag, msg, tr);
+ }
+ }
+
+ public static void vfmt(String tag, String fmt, Object... args) {
+ if (ENABLE_VERBOSE) {
+ String msg = String.format(Locale.US, fmt, args);
+ Log.v(tag, msg);
+ }
+ }
+
+ public static void printStackTrace(Throwable e) {
+ if (ENABLE_WARN) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void printCause(Throwable e) {
+ if (ENABLE_WARN) {
+ Throwable cause = e.getCause();
+ if (cause != null)
+ e = cause;
+
+ printStackTrace(e);
+ }
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/pragma/Pragma.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/pragma/Pragma.java
new file mode 100644
index 0000000..df26120
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/pragma/Pragma.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2013 Zhang Rui
+ *
+ * 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.pragma;
+
+/*-
+ * configurated by app project
+ */
+public class Pragma {
+ public static final boolean ENABLE_VERBOSE = true;
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/FileMediaDataSource.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/FileMediaDataSource.java
new file mode 100644
index 0000000..0b007ff
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/FileMediaDataSource.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.widget.media;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import tv.danmaku.ijk.media.player.misc.IMediaDataSource;
+
+public class FileMediaDataSource implements IMediaDataSource {
+ private RandomAccessFile mFile;
+ private long mFileSize;
+
+ public FileMediaDataSource(File file) throws IOException {
+ mFile = new RandomAccessFile(file, "r");
+ mFileSize = mFile.length();
+ }
+
+ @Override
+ public int readAt(long position, byte[] buffer, int offset, int size) throws IOException {
+ if (mFile.getFilePointer() != position)
+ mFile.seek(position);
+
+ if (size == 0)
+ return 0;
+
+ return mFile.read(buffer, 0, size);
+ }
+
+ @Override
+ public long getSize() throws IOException {
+ return mFileSize;
+ }
+
+ @Override
+ public void close() throws IOException {
+ mFileSize = 0;
+ mFile.close();
+ mFile = null;
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/IMediaController.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/IMediaController.java
new file mode 100644
index 0000000..5e84c97
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/IMediaController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.widget.media;
+
+import android.view.View;
+import android.widget.MediaController;
+
+public interface IMediaController {
+ void hide();
+
+ boolean isShowing();
+
+ void setAnchorView(View view);
+
+ void setEnabled(boolean enabled);
+
+ void setMediaPlayer(MediaController.MediaPlayerControl player);
+
+ void show(int timeout);
+
+ void show();
+
+ //----------
+ // Extends
+ //----------
+ void showOnce(View view);
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/IRenderView.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/IRenderView.java
new file mode 100644
index 0000000..fd0164f
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/IRenderView.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.widget.media;
+
+import android.graphics.SurfaceTexture;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.View;
+
+import tv.danmaku.ijk.media.player.IMediaPlayer;
+
+public interface IRenderView {
+ int AR_ASPECT_FIT_PARENT = 0; // without clip
+ int AR_ASPECT_FILL_PARENT = 1; // may clip
+ int AR_ASPECT_WRAP_CONTENT = 2;
+ int AR_MATCH_PARENT = 3;
+ int AR_16_9_FIT_PARENT = 4;
+ int AR_4_3_FIT_PARENT = 5;
+
+ int AR_1_1 = 6;
+
+ View getView();
+
+ boolean shouldWaitForResize();
+
+ void setVideoSize(int videoWidth, int videoHeight);
+
+ void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen);
+
+ void setVideoRotation(int degree);
+
+ void setAspectRatio(int aspectRatio);
+
+ void addRenderCallback( IRenderCallback callback);
+
+ void removeRenderCallback( IRenderCallback callback);
+
+ interface ISurfaceHolder {
+ void bindToMediaPlayer(IMediaPlayer mp);
+
+ IRenderView getRenderView();
+
+ SurfaceHolder getSurfaceHolder();
+
+ Surface openSurface();
+
+ SurfaceTexture getSurfaceTexture();
+ }
+
+ interface IRenderCallback {
+ /**
+ * @param holder
+ * @param width could be 0
+ * @param height could be 0
+ */
+ void onSurfaceCreated(ISurfaceHolder holder, int width, int height);
+
+ /**
+ * @param holder
+ * @param format could be 0
+ * @param width
+ * @param height
+ */
+ void onSurfaceChanged( ISurfaceHolder holder, int format, int width, int height);
+
+ void onSurfaceDestroyed( ISurfaceHolder holder);
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/IjkVideoView.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/IjkVideoView.java
new file mode 100644
index 0000000..aa3fd6a
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/IjkVideoView.java
@@ -0,0 +1,1357 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.widget.media;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Build;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.TextureView;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.MediaController;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import tv.danmaku.ijk.media.Settings;
+import tv.danmaku.ijk.media.player.AndroidMediaPlayer;
+import tv.danmaku.ijk.media.player.IMediaPlayer;
+import tv.danmaku.ijk.media.player.IjkMediaPlayer;
+import tv.danmaku.ijk.media.player.R;
+import tv.danmaku.ijk.media.player.TextureMediaPlayer;
+import tv.danmaku.ijk.media.player.misc.IMediaDataSource;
+import tv.danmaku.ijk.media.player.misc.ITrackInfo;
+
+/**
+ * TODO
+ * */
+public class IjkVideoView extends FrameLayout implements MediaController.MediaPlayerControl {
+ private String TAG = "VideoView";
+
+ // settable by the client
+ protected Uri mUri;
+
+ private Map mHeaders;
+ public static String Player_KEY = "EasyPlayer is free!";
+
+ // all possible internal states
+ private static final int STATE_ERROR = -1;
+ private static final int STATE_IDLE = 0;
+ private static final int STATE_PREPARING = 1;
+ private static final int STATE_PREPARED = 2;
+ private static final int STATE_PLAYING = 3;
+ private static final int STATE_PAUSED = 4;
+ private static final int STATE_PLAYBACK_COMPLETED = 5;
+
+ // mCurrentState is a VideoView object's current state.
+ // mTargetState is the state that a method caller intends to reach.
+ // For instance, regardless the VideoView object's current state,
+ // calling pause() intends to bring the object to a target state
+ // of STATE_PAUSED.
+ private int mCurrentState = STATE_IDLE;
+ private int mTargetState = STATE_IDLE;
+
+ // All the stuff we need for playing and showing a video
+ private IRenderView.ISurfaceHolder mSurfaceHolder = null;
+ protected IMediaPlayer mMediaPlayer = null;
+
+ // private int mAudioSession;
+ private int mVideoWidth;
+ private int mVideoHeight;
+ private int mSurfaceWidth;
+ private int mSurfaceHeight;
+ private int mVideoRotationDegree;
+
+ private IMediaController mMediaController;
+ private IMediaPlayer.OnCompletionListener mOnCompletionListener;
+ private IMediaPlayer.OnPreparedListener mOnPreparedListener;
+ private IMediaPlayer.OnErrorListener mOnErrorListener;
+ private IMediaPlayer.OnInfoListener mOnInfoListener;
+
+ private int mCurrentBufferPercentage;
+ private int mSeekWhenPrepared; // recording the seek position while preparing
+
+ private boolean mCanPause = true;
+ private boolean mCanSeekBack = true;
+ private boolean mCanSeekForward = true;
+
+ /** Subtitle rendering widget overlaid on top of the video. */
+ // private RenderingWidget mSubtitleWidget;
+
+ /**
+ * Listener for changes to subtitle data, used to redraw when needed.
+ */
+ // private RenderingWidget.OnChangedListener mSubtitlesChangedListener;
+
+ private Context mAppContext;
+ private Settings mSettings;
+ private IRenderView mRenderView;
+
+ private int mVideoSarNum;
+ private int mVideoSarDen;
+
+ private long mPrepareStartTime = 0;
+ private long mPrepareEndTime = 0;
+ private long mSeekStartTime = 0;
+ private long mSeekEndTime = 0;
+
+ private ImageView mCover;
+ private boolean mShowing = true;
+
+ public IjkVideoView(Context context) {
+ super(context);
+ initVideoView(context);
+ }
+
+ public IjkVideoView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initVideoView(context);
+ }
+
+ public IjkVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initVideoView(context);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public IjkVideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ initVideoView(context);
+ }
+
+ // REMOVED: onMeasure
+ // REMOVED: onInitializeAccessibilityEvent
+ // REMOVED: onInitializeAccessibilityNodeInfo
+ // REMOVED: resolveAdjustedSize
+
+ private void initVideoView(Context context) {
+ mAppContext = context.getApplicationContext();
+ mSettings = new Settings(mAppContext);
+
+ initRenders();
+
+ mVideoWidth = 0;
+ mVideoHeight = 0;
+ // REMOVED: getHolder().addCallback(mSHCallback);
+ // REMOVED: getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ requestFocus();
+ // REMOVED: mPendingSubtitleTracks = new Vector>();
+ mCurrentState = STATE_IDLE;
+ mTargetState = STATE_IDLE;
+ }
+
+ // REMOVED: addSubtitleSource
+ // REMOVED: mPendingSubtitleTracks
+
+ public void stopPlayback() {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.record(null, 0);
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ mCurrentState = STATE_IDLE;
+ mTargetState = STATE_IDLE;
+ AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
+ am.abandonAudioFocus(null);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ private void openVideo() {
+ if (mUri == null || mSurfaceHolder == null) {
+ // not ready for playback just yet, will try again later
+ return;
+ }
+
+ // we shouldn't clear the target state, because somebody might have
+ // called start() previously
+ release(false);
+
+ AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
+ am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+
+ try {
+ mMediaPlayer = createPlayer(mSettings.getPlayer());
+ String scheme = mUri.getScheme();
+
+ if (scheme == null)
+ scheme = "";
+
+ if (mMediaPlayer instanceof IjkMediaPlayer) {
+ IjkMediaPlayer ijk = (IjkMediaPlayer) mMediaPlayer;
+
+ if (scheme.equalsIgnoreCase("rtsp")) {
+ ijk.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "iformat", "rtsp");
+ } else if (scheme.equalsIgnoreCase("rtmp")) {
+ ijk.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "iformat", "flv");
+ } else if (scheme.equalsIgnoreCase("http")) {
+ if (mUri.getPath().endsWith(".m3u8")) {
+ ijk.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "iformat", "hls");
+ }
+ }
+ }
+
+ // TODO: create SubtitleController in MediaPlayer, but we need
+ // a context for the subtitle renderers
+// final Context context = getContext();
+ // REMOVED: SubtitleController
+
+ // REMOVED: mAudioSession
+ mMediaPlayer.setOnPreparedListener(mPreparedListener);
+ mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
+ mMediaPlayer.setOnCompletionListener(mCompletionListener);
+ mMediaPlayer.setOnErrorListener(mErrorListener);
+ mMediaPlayer.setOnInfoListener(mInfoListener);
+ mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
+ mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener);
+ mCurrentBufferPercentage = 0;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+ mSettings.getUsingMediaDataSource() &&
+ (TextUtils.isEmpty(scheme) || scheme.equalsIgnoreCase("file"))) {
+ IMediaDataSource dataSource = new FileMediaDataSource(new File(mUri.toString()));
+ mMediaPlayer.setDataSource(dataSource);
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ mMediaPlayer.setDataSource(mAppContext, mUri, mHeaders);
+ } else {
+ mMediaPlayer.setDataSource(mUri.toString());
+ }
+// if (mShowing)
+ bindSurfaceHolder(mMediaPlayer, mSurfaceHolder);
+ mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mMediaPlayer.setScreenOnWhilePlaying(true);
+ mPrepareStartTime = System.currentTimeMillis();
+ mMediaPlayer.prepareAsync();
+ // REMOVED: mPendingSubtitleTracks
+
+ // we don't set the target state here either, but preserve the
+ // target state that was there before.
+ mCurrentState = STATE_PREPARING;
+ attachMediaController();
+ } catch (IOException ex) {
+ Log.w(TAG, "Unable to open content: " + mUri, ex);
+ mCurrentState = STATE_ERROR;
+ mTargetState = STATE_ERROR;
+ mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ } catch (IllegalArgumentException ex) {
+ Log.w(TAG, "Unable to open content: " + mUri, ex);
+ mCurrentState = STATE_ERROR;
+ mTargetState = STATE_ERROR;
+ mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
+ } finally {
+ // REMOVED: mPendingSubtitleTracks.clear();
+ }
+ }
+
+ public void setMediaController(IMediaController controller) {
+ if (mMediaController != null) {
+ mMediaController.hide();
+ }
+
+ mMediaController = controller;
+ attachMediaController();
+ }
+
+ private void attachMediaController() {
+ if (mMediaPlayer != null && mMediaController != null) {
+ mMediaController.setMediaPlayer(this);
+ View anchorView = this.getParent() instanceof View ?
+ (View) this.getParent() : this;
+ mMediaController.setAnchorView(anchorView);
+ mMediaController.setEnabled(isInPlaybackState());
+ }
+ }
+
+ IMediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = new IMediaPlayer.OnVideoSizeChangedListener() {
+ public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, int sarNum, int sarDen) {
+ mVideoWidth = mp.getVideoWidth();
+ mVideoHeight = mp.getVideoHeight();
+ mVideoSarNum = mp.getVideoSarNum();
+ mVideoSarDen = mp.getVideoSarDen();
+
+ if (mVideoWidth != 0 && mVideoHeight != 0) {
+ if (mRenderView != null) {
+ mRenderView.setVideoSize(mVideoWidth, mVideoHeight);
+ mRenderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen);
+ }
+
+ // REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight);
+ requestLayout();
+ }
+ }
+ };
+
+ IMediaPlayer.OnPreparedListener mPreparedListener = new IMediaPlayer.OnPreparedListener() {
+ public void onPrepared(IMediaPlayer mp) {
+ mPrepareEndTime = System.currentTimeMillis();
+ mCurrentState = STATE_PREPARED;
+
+ // Get the capabilities of the player for this stream
+ // REMOVED: Metadata
+
+ if (mOnPreparedListener != null) {
+ mOnPreparedListener.onPrepared(mMediaPlayer);
+ }
+
+ if (mMediaController != null) {
+ mMediaController.setEnabled(true);
+ }
+
+ mVideoWidth = mp.getVideoWidth();
+ mVideoHeight = mp.getVideoHeight();
+
+ int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call
+
+ if (seekToPosition != 0) {
+ seekTo(seekToPosition);
+ }
+
+ if (mVideoWidth != 0 && mVideoHeight != 0) {
+ //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
+ // REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight);
+ if (mRenderView != null) {
+ mRenderView.setVideoSize(mVideoWidth, mVideoHeight);
+ mRenderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen);
+
+ if (!mRenderView.shouldWaitForResize() || mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
+ // We didn't actually change the size (it was already at the size
+ // we need), so we won't get a "surface changed" callback, so
+ // start the video here instead of in the callback.
+
+ if (mTargetState == STATE_PLAYING) {
+ start();
+
+ if (mMediaController != null) {
+ mMediaController.show();
+ }
+ } else if (!isPlaying() && (seekToPosition != 0 || getCurrentPosition() > 0)) {
+ if (mMediaController != null) {
+ // Show the media controls when we're paused into a video and make 'em stick.
+ mMediaController.show(0);
+ }
+ }
+ }
+ }
+ } else {
+ // We don't know the video size yet, but should start anyway.
+ // The video size might be reported to us later.
+ if (mTargetState == STATE_PLAYING) {
+ start();
+ }
+ }
+ }
+ };
+
+ private IMediaPlayer.OnCompletionListener mCompletionListener = new IMediaPlayer.OnCompletionListener() {
+ public void onCompletion(IMediaPlayer mp) {
+ mCurrentState = STATE_PLAYBACK_COMPLETED;
+ mTargetState = STATE_PLAYBACK_COMPLETED;
+
+ if (mMediaController != null) {
+ mMediaController.hide();
+ }
+
+ if (mOnCompletionListener != null) {
+ mOnCompletionListener.onCompletion(mMediaPlayer);
+ }
+ }
+ };
+
+ private IMediaPlayer.OnInfoListener mInfoListener = new IMediaPlayer.OnInfoListener() {
+ public boolean onInfo(IMediaPlayer mp, int arg1, int arg2) {
+ if (mOnInfoListener != null) {
+ mOnInfoListener.onInfo(mp, arg1, arg2);
+ }
+
+ switch (arg1) {
+ case IMediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING:
+ Log.d(TAG, "MEDIA_INFO_VIDEO_TRACK_LAGGING:");
+ break;
+ case IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:
+ Log.d(TAG, "MEDIA_INFO_VIDEO_RENDERING_START:");
+ mCover.animate().cancel();
+ mCover.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCover.setVisibility(GONE);
+ mCover.setAlpha(1.0f);
+ }
+ });
+ break;
+ case IMediaPlayer.MEDIA_INFO_BUFFERING_START:
+ Log.d(TAG, "MEDIA_INFO_BUFFERING_START:");
+ break;
+ case IMediaPlayer.MEDIA_INFO_BUFFERING_END:
+ Log.d(TAG, "MEDIA_INFO_BUFFERING_END:");
+ break;
+ case IMediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH:
+ Log.d(TAG, "MEDIA_INFO_NETWORK_BANDWIDTH: " + arg2);
+ break;
+ case IMediaPlayer.MEDIA_INFO_BAD_INTERLEAVING:
+ Log.d(TAG, "MEDIA_INFO_BAD_INTERLEAVING:");
+ break;
+ case IMediaPlayer.MEDIA_INFO_NOT_SEEKABLE:
+ Log.d(TAG, "MEDIA_INFO_NOT_SEEKABLE:");
+ break;
+ case IMediaPlayer.MEDIA_INFO_METADATA_UPDATE:
+ Log.d(TAG, "MEDIA_INFO_METADATA_UPDATE:");
+ break;
+ case IMediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE:
+ Log.d(TAG, "MEDIA_INFO_UNSUPPORTED_SUBTITLE:");
+ break;
+ case IMediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT:
+ Log.d(TAG, "MEDIA_INFO_SUBTITLE_TIMED_OUT:");
+ break;
+ case IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED:
+ mVideoRotationDegree = arg2;
+ Log.d(TAG, "MEDIA_INFO_VIDEO_ROTATION_CHANGED: " + arg2);
+ if (mRenderView != null)
+ mRenderView.setVideoRotation(arg2);
+ break;
+ case IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START:
+ Log.d(TAG, "MEDIA_INFO_AUDIO_RENDERING_START:");
+ break;
+ }
+ return true;
+ }
+ };
+
+ private IMediaPlayer.OnErrorListener mErrorListener = new IMediaPlayer.OnErrorListener() {
+ public boolean onError(IMediaPlayer mp, int framework_err, int impl_err) {
+ Log.d(TAG, "Error: " + framework_err + "," + impl_err);
+ mCurrentState = STATE_ERROR;
+ mTargetState = STATE_ERROR;
+ if (mMediaController != null) {
+ mMediaController.hide();
+ }
+
+ /* If an error handler has been supplied, use it and finish. */
+ if (mOnErrorListener != null) {
+ if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {
+ return true;
+ }
+ }
+ return true;
+ }
+ };
+
+ private IMediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = new IMediaPlayer.OnBufferingUpdateListener() {
+ public void onBufferingUpdate(IMediaPlayer mp, int percent) {
+ mCurrentBufferPercentage = percent;
+ }
+ };
+
+ private IMediaPlayer.OnSeekCompleteListener mSeekCompleteListener = new IMediaPlayer.OnSeekCompleteListener() {
+ @Override
+ public void onSeekComplete(IMediaPlayer mp) {
+ mSeekEndTime = System.currentTimeMillis();
+ }
+ };
+
+ public void reStart(){
+ openVideo();
+ }
+
+ /**
+ * Register a callback to be invoked when the media file
+ * is loaded and ready to go.
+ *
+ * @param l The callback that will be run
+ */
+ public void setOnPreparedListener(IMediaPlayer.OnPreparedListener l) {
+ mOnPreparedListener = l;
+ }
+
+ /**
+ * Register a callback to be invoked when the end of a media file
+ * has been reached during playback.
+ *
+ * @param l The callback that will be run
+ */
+ public void setOnCompletionListener(IMediaPlayer.OnCompletionListener l) {
+ mOnCompletionListener = l;
+ }
+
+ /**
+ * Register a callback to be invoked when an error occurs
+ * during playback or setup. If no listener is specified,
+ * or if the listener returned false, VideoView will inform
+ * the user of any errors.
+ *
+ * @param l The callback that will be run
+ */
+ public void setOnErrorListener(IMediaPlayer.OnErrorListener l) {
+ mOnErrorListener = l;
+ }
+
+ /**
+ * Register a callback to be invoked when an informational event
+ * occurs during playback or setup.
+ *
+ * @param l The callback that will be run
+ */
+ public void setOnInfoListener(IMediaPlayer.OnInfoListener l) {
+ mOnInfoListener = l;
+ }
+
+ // REMOVED: mSHCallback
+ private void bindSurfaceHolder(IMediaPlayer mp, IRenderView.ISurfaceHolder holder) {
+ if (mp == null)
+ return;
+
+ if (holder == null) {
+ mp.setDisplay(null);
+ return;
+ }
+
+ holder.bindToMediaPlayer(mp);
+ }
+
+ IRenderView.IRenderCallback mSHCallback = new IRenderView.IRenderCallback() {
+ @Override
+ public void onSurfaceChanged( IRenderView.ISurfaceHolder holder, int format, int w, int h) {
+ if (holder.getRenderView() != mRenderView) {
+ Log.e(TAG, "onSurfaceChanged: unmatched render callback\n");
+ return;
+ }
+
+ mSurfaceWidth = w;
+ mSurfaceHeight = h;
+ boolean isValidState = (mTargetState == STATE_PLAYING);
+ boolean hasValidSize = !mRenderView.shouldWaitForResize() || (mVideoWidth == w && mVideoHeight == h);
+
+ if (mMediaPlayer != null && isValidState && hasValidSize) {
+ if (mSeekWhenPrepared != 0) {
+ seekTo(mSeekWhenPrepared);
+ }
+
+ start();
+ }
+ }
+
+ @Override
+ public void onSurfaceCreated( IRenderView.ISurfaceHolder holder, int width, int height) {
+ if (holder.getRenderView() != mRenderView) {
+ Log.e(TAG, "onSurfaceCreated: unmatched render callback\n");
+ return;
+ }
+
+ if (mCurrentState == STATE_PLAYING) {
+ mCover.animate().cancel();
+ mCover.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCover.setVisibility(GONE);
+ mCover.setAlpha(1.0f);
+ }
+ });
+ }
+
+ mSurfaceHolder = holder;
+
+ if (mMediaPlayer != null) {
+// if (mShowing)
+ bindSurfaceHolder(mMediaPlayer, holder);
+// mCover.animate().setListener(null);
+// mRenderView.getView().setAlpha(0);
+// mRenderView.getView().animate().withLayer().alpha(1.0f);
+
+ } else {
+ openVideo();
+ }
+ }
+
+ @Override
+ public void onSurfaceDestroyed( IRenderView.ISurfaceHolder holder) {
+ if (holder.getRenderView() != mRenderView) {
+ Log.e(TAG, "onSurfaceDestroyed: unmatched render callback\n");
+ return;
+ }
+
+ // after we return from this we can't use the surface any more
+ mSurfaceHolder = null;
+
+ // REMOVED: if (mMediaController != null) mMediaController.hide();
+ // REMOVED: release(true);
+ releaseWithoutStop();
+ }
+ };
+
+ public void releaseWithoutStop() {
+ if (mMediaPlayer != null)
+ mMediaPlayer.setDisplay(null);
+ }
+
+ /*
+ * release the media player in any state
+ */
+ public void release(boolean cleartargetstate) {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.reset();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+
+ // REMOVED: mPendingSubtitleTracks.clear();
+ mCurrentState = STATE_IDLE;
+
+ if (cleartargetstate) {
+ mTargetState = STATE_IDLE;
+ }
+
+ AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
+ am.abandonAudioFocus(null);
+ }
+ }
+
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ final int delayMillis = 30 * 60 * 1000;
+
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ setVideoURI(mUri);
+ toggleRender();
+ postDelayed(this, delayMillis);
+ }
+ }, delayMillis);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (isInPlaybackState() && mMediaController != null) {
+ toggleMediaControlsVisibility();
+ return true;
+ }
+
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent ev) {
+ if (isInPlaybackState() && mMediaController != null) {
+ toggleMediaControlsVisibility();
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
+ keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
+ keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
+ keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
+ keyCode != KeyEvent.KEYCODE_MENU &&
+ keyCode != KeyEvent.KEYCODE_CALL &&
+ keyCode != KeyEvent.KEYCODE_ENDCALL;
+
+ if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
+ if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
+ if (mMediaPlayer.isPlaying()) {
+ pause();
+ mMediaController.show();
+ } else {
+ start();
+ mMediaController.hide();
+ }
+
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
+ if (!mMediaPlayer.isPlaying()) {
+ start();
+ mMediaController.hide();
+ }
+
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
+ if (mMediaPlayer.isPlaying()) {
+ pause();
+ mMediaController.show();
+ }
+
+ return true;
+ } else {
+ toggleMediaControlsVisibility();
+ }
+ }
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ public void toggleMediaControlsVisibility() {
+ if (mMediaController == null)
+ return;
+
+ if (mMediaController.isShowing()) {
+ mMediaController.hide();
+ } else {
+ mMediaController.show();
+ }
+ }
+
+ public int getSurfaceWidth() {
+ if (mRenderView == null)
+ return 0;
+
+ return mRenderView.getView().getWidth();
+ }
+
+ public int getSurfaceHeight() {
+ if (mRenderView == null)
+ return 0;
+
+ return mRenderView.getView().getHeight();
+ }
+
+ @Override
+ public void start() {
+ if (isInPlaybackState()) {
+ mMediaPlayer.start();
+ mCurrentState = STATE_PLAYING;
+ }
+
+ mTargetState = STATE_PLAYING;
+ }
+
+ @Override
+ public void pause() {
+ if (isInPlaybackState()) {
+ if (mMediaPlayer.isPlaying()) {
+ mMediaPlayer.pause();
+ mCurrentState = STATE_PAUSED;
+ }
+ }
+
+ mTargetState = STATE_PAUSED;
+ }
+
+ public void suspend() {
+ release(false);
+ }
+
+ public void resume() {
+ openVideo();
+ }
+
+ @Override
+ public int getDuration() {
+ if (isInPlaybackState()) {
+ return (int) mMediaPlayer.getDuration();
+ }
+
+ return -1;
+ }
+
+ @Override
+ public int getCurrentPosition() {
+ if (isInPlaybackState()) {
+ long position = mMediaPlayer.getCurrentPosition();
+ return (int) position;
+ }
+
+ return 0;
+ }
+
+ @Override
+ public void seekTo(int msec) {
+ if (isInPlaybackState()) {
+ mSeekStartTime = System.currentTimeMillis();
+ mMediaPlayer.seekTo(msec);
+ mSeekWhenPrepared = 0;
+ } else {
+ mSeekWhenPrepared = msec;
+ }
+ }
+
+ @Override
+ public boolean isPlaying() {
+ return isInPlaybackState() && mMediaPlayer.isPlaying();
+ }
+
+ @Override
+ public int getBufferPercentage() {
+ if (mMediaPlayer != null) {
+ return mCurrentBufferPercentage;
+ }
+
+ return 0;
+ }
+
+ public boolean isInPlaybackState() {
+ return (mMediaPlayer != null && mCurrentState != STATE_ERROR &&
+ mCurrentState != STATE_IDLE && mCurrentState != STATE_PREPARING);
+ }
+
+ @Override
+ public boolean canPause() {
+ return mCanPause;
+ }
+
+ @Override
+ public boolean canSeekBackward() {
+ return mCanSeekBack;
+ }
+
+ @Override
+ public boolean canSeekForward() {
+ return mCanSeekForward;
+ }
+
+ @Override
+ public int getAudioSessionId() {
+ return 0;
+ }
+
+ // REMOVED: getAudioSessionId();
+ // REMOVED: onAttachedToWindow();
+ // REMOVED: onDetachedFromWindow();
+ // REMOVED: onLayout();
+ // REMOVED: draw();
+ // REMOVED: measureAndLayoutSubtitleWidget();
+ // REMOVED: setSubtitleWidget();
+ // REMOVED: getSubtitleLooper();
+
+ //-------------------------
+ // Extend: Aspect Ratio
+ //-------------------------
+
+ private static final int[] s_allAspectRatio = {
+ IRenderView.AR_ASPECT_FIT_PARENT,
+ IRenderView.AR_ASPECT_FILL_PARENT,
+// IRenderView.AR_ASPECT_WRAP_CONTENT,
+ IRenderView.AR_MATCH_PARENT,
+// IRenderView.AR_16_9_FIT_PARENT,
+// IRenderView.AR_4_3_FIT_PARENT
+ };
+
+ private int mCurrentAspectRatioIndex = 0;
+ private int mCurrentAspectRatio = s_allAspectRatio[0];
+
+ public int toggleAspectRatio() {
+ mCurrentAspectRatioIndex++;
+ mCurrentAspectRatioIndex %= s_allAspectRatio.length;
+
+ switch (mCurrentAspectRatio) {
+ case IRenderView.AR_ASPECT_FIT_PARENT:
+ mCover.setScaleType(ImageView.ScaleType.FIT_CENTER);
+ case IRenderView.AR_ASPECT_FILL_PARENT:
+// IRenderView.AR_ASPECT_WRAP_CONTENT,
+ mCover.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ case IRenderView.AR_MATCH_PARENT:
+ mCover.setScaleType(ImageView.ScaleType.FIT_XY);
+ }
+
+ mCurrentAspectRatio = s_allAspectRatio[mCurrentAspectRatioIndex];
+
+ if (mRenderView != null)
+ mRenderView.setAspectRatio(mCurrentAspectRatio);
+
+ return mCurrentAspectRatio;
+ }
+
+ //-------------------------
+ // Extend: Render
+ //-------------------------
+ public static final int RENDER_NONE = 0;
+ public static final int RENDER_SURFACE_VIEW = 1;
+ public static final int RENDER_TEXTURE_VIEW = 2;
+
+ private List mAllRenders = new ArrayList<>();
+ private int mCurrentRenderIndex = 0;
+ private int mCurrentRender = RENDER_NONE;
+
+ private void initRenders() {
+ mAllRenders.clear();
+
+ if (mSettings.getEnableTextureView() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ mAllRenders.add(RENDER_TEXTURE_VIEW);
+ if (mSettings.getEnableSurfaceView())
+ mAllRenders.add(RENDER_SURFACE_VIEW);
+ if (mSettings.getEnableNoView())
+ mAllRenders.add(RENDER_NONE);
+
+ if (mAllRenders.isEmpty())
+ mAllRenders.add(RENDER_SURFACE_VIEW);
+
+ mCurrentRender = mAllRenders.get(mCurrentRenderIndex);
+ setRender(mCurrentRender);
+
+ mCover = new ImageView(getContext());
+ addView(mCover);
+
+ switch (mCurrentAspectRatio) {
+ case IRenderView.AR_ASPECT_FIT_PARENT:
+ mCover.setScaleType(ImageView.ScaleType.FIT_CENTER);
+ break;
+ case IRenderView.AR_ASPECT_FILL_PARENT:
+// IRenderView.AR_ASPECT_WRAP_CONTENT,
+ mCover.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ break;
+ case IRenderView.AR_MATCH_PARENT:
+ mCover.setScaleType(ImageView.ScaleType.FIT_XY);
+ break;
+ }
+ }
+
+ public int toggleRender() {
+ mCurrentRenderIndex++;
+ mCurrentRenderIndex %= mAllRenders.size();
+
+ mCurrentRender = mAllRenders.get(mCurrentRenderIndex);
+ setRender(mCurrentRender);
+ return mCurrentRender;
+ }
+
+ public static String getRenderText(Context context, int render) {
+ String text;
+
+ switch (render) {
+ case RENDER_NONE:
+ text = context.getString(R.string.VideoView_render_none);
+ break;
+ case RENDER_SURFACE_VIEW:
+ text = context.getString(R.string.VideoView_render_surface_view);
+ break;
+ case RENDER_TEXTURE_VIEW:
+ text = context.getString(R.string.VideoView_render_texture_view);
+ break;
+ default:
+ text = context.getString(R.string.N_A);
+ break;
+ }
+
+ return text;
+ }
+
+ //-------------------------
+ // Extend: Player
+ //-------------------------
+ public int togglePlayer() {
+ if (mMediaPlayer != null)
+ mMediaPlayer.release();
+
+ if (mRenderView != null)
+ mRenderView.getView().invalidate();
+
+ openVideo();
+
+ return mSettings.getPlayer();
+ }
+
+ public IMediaPlayer createPlayer(int playerType) {
+ IMediaPlayer mediaPlayer = null;
+
+ switch (playerType) {
+ case Settings.PV_PLAYER__AndroidMediaPlayer: {
+ AndroidMediaPlayer androidMediaPlayer = new AndroidMediaPlayer();
+ mediaPlayer = androidMediaPlayer;
+ }
+ break;
+ case Settings.PV_PLAYER__IjkMediaPlayer:
+ default: {
+ IjkMediaPlayer ijkMediaPlayer = null;
+
+ if (mUri != null) {
+ ijkMediaPlayer = new IjkMediaPlayer(getContext().getApplicationContext(), Player_KEY);
+ ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG);
+
+ if (mSettings.getUsingMediaCodec()) {
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);
+
+ if (mSettings.getUsingMediaCodecAutoRotate()) {
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1);
+ } else {
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 0);
+ }
+
+ if (mSettings.getMediaCodecHandleResolutionChange()) {
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 1);
+ } else {
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 0);
+ }
+ } else {
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0);
+ }
+
+// ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);
+ if (mSettings.getUsingOpenSLES()) {
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 1);
+ } else {
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 0);
+ }
+
+ String pixelFormat = mSettings.getPixelFormat();
+
+ if (TextUtils.isEmpty(pixelFormat)) {
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32);
+ } else {
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", pixelFormat);
+ }
+
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", PreferenceManager.getDefaultSharedPreferences(getContext()).getBoolean("USE_UDP_MODE", false) ? "udp":"tcp");
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", PreferenceManager.getDefaultSharedPreferences(getContext()).getLong("analyzeduration", 1000000L));//21s
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", PreferenceManager.getDefaultSharedPreferences(getContext()).getLong("probesize", 204800));//32byte
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "auto_convert", 0);
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "reconnect", 1);
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "timeout", PreferenceManager.getDefaultSharedPreferences(getContext()).getInt("timeout", 10));
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags","nobuffer");
+
+// // framedrop 是在视频帧处理不过来的时候丢弃一些帧达到同步的效果
+// ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 30);
+
+ // 清空DNS,有時因爲在APP裏面要播放多種類型的視頻(如:MP4,直播,直播平臺保存的視頻,和其他http視頻), 有時會造成因爲DNS的問題而報10000問題的
+ ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear", 1);
+
+// ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "iformat", "hls");
+// ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1);
+
+// pause output until enough packets have been read after stalling
+ }
+ mediaPlayer = ijkMediaPlayer;
+ }
+ break;
+ }
+
+ if (mSettings.getEnableDetachedSurfaceTextureView()) {
+ mediaPlayer = new TextureMediaPlayer(mediaPlayer);
+ }
+
+ return mediaPlayer;
+ }
+
+ public String takePicture(String path) {
+ try {
+ int videoWidth = mVideoWidth;
+ int videoHeight = mVideoHeight;
+
+ if (videoWidth <= 0 || videoHeight <= 0) {
+ return path;
+ }
+
+ Bitmap bitmap = Bitmap.createBitmap(videoWidth, videoHeight, Bitmap.Config.ARGB_8888);
+
+ if (mRenderView instanceof TextureRenderView){
+ TextureRenderView render = (TextureRenderView) mRenderView;
+ render.getBitmap(bitmap);
+
+ saveBitmapInFile(path, bitmap);
+
+ bitmap.recycle();
+ return path;
+ }
+ } catch (OutOfMemoryError error) {
+ error.printStackTrace();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ private void saveBitmapInFile(String path, Bitmap bitmap) {
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(path);
+
+ // 压缩图像
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (OutOfMemoryError error) {
+ error.printStackTrace();
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ public void startRecord(String path,int seconds) {
+ if (mMediaPlayer == null){
+ throw new IllegalStateException("should in play state!");
+ }
+ if (TextUtils.isEmpty(path)){
+ throw new IllegalArgumentException("path should not be null!");
+ }
+ mMediaPlayer.record(path,seconds);
+ }
+
+ public void stopRecord() {
+ if (mMediaPlayer == null){
+ throw new IllegalStateException("should in play state!");
+ }
+
+ mMediaPlayer.record(null, 0);
+ }
+
+ public long getReceivedBytes() {
+ if (mMediaPlayer == null || !(mMediaPlayer instanceof IjkMediaPlayer))
+ return 0;
+
+ return ((IjkMediaPlayer)mMediaPlayer).getReceivedBytes();
+ }
+
+ public long getVideoCachePackets() {
+ if (mMediaPlayer == null || !(mMediaPlayer instanceof IjkMediaPlayer))
+ return 0;
+
+ return ((IjkMediaPlayer)mMediaPlayer).getReceivedVideoFrames();
+ }
+
+ public void setShowing(boolean showing) {
+ mShowing = showing;
+
+ if (mMediaPlayer != null){
+// toggleRender();
+
+ if (mShowing) {
+// bindSurfaceHolder(mMediaPlayer, mSurfaceHolder);
+// mSurfaceHolder.getSurfaceHolder().setFixedSize(mSurfaceWidth,mSurfaceHeight);
+// mRenderView.getView().layout(0,0,getWidth(),getHeight());
+
+ mRenderView.setAspectRatio(mCurrentAspectRatio);
+ } else {
+// mMediaPlayer.setDisplay(null);
+ mRenderView.setAspectRatio(IRenderView.AR_1_1);
+// mSurfaceHolder.getSurfaceHolder().setFixedSize(1,1);
+ }
+ }
+ }
+
+ public boolean isShowing() {
+ return mShowing;
+ }
+
+ public void setRenderView(IRenderView renderView) {
+ if (mRenderView != null) {
+ if (mRenderView instanceof TextureView){
+ Bitmap bmp = ((TextureView) mRenderView).getBitmap();
+ mCover.setImageBitmap(bmp);
+// mCover.setVisibility(VISIBLE);
+ }
+
+// if (mMediaPlayer != null)
+// mMediaPlayer.setDisplay(null);
+
+ View renderUIView = mRenderView.getView();
+ mRenderView.removeRenderCallback(mSHCallback);
+ mRenderView = null;
+ removeView(renderUIView);
+ }
+
+ if (renderView == null)
+ return;
+
+ mRenderView = renderView;
+ renderView.setAspectRatio(mCurrentAspectRatio);
+
+ if (mVideoWidth > 0 && mVideoHeight > 0)
+ renderView.setVideoSize(mVideoWidth, mVideoHeight);
+ if (mVideoSarNum > 0 && mVideoSarDen > 0)
+ renderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen);
+
+ View renderUIView = mRenderView.getView();
+ LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER);
+ renderUIView.setLayoutParams(lp);
+ addView(renderUIView, 0);
+
+ mRenderView.addRenderCallback(mSHCallback);
+ mRenderView.setVideoRotation(mVideoRotationDegree);
+ }
+
+ public void setRender(int render) {
+ switch (render) {
+ case RENDER_NONE:
+ setRenderView(null);
+ break;
+ case RENDER_TEXTURE_VIEW: {
+ TextureRenderView renderView = new TextureRenderView(getContext());
+
+ if (mMediaPlayer != null) {
+ renderView.getSurfaceHolder().bindToMediaPlayer(mMediaPlayer);
+ renderView.setVideoSize(mMediaPlayer.getVideoWidth(), mMediaPlayer.getVideoHeight());
+ renderView.setVideoSampleAspectRatio(mMediaPlayer.getVideoSarNum(), mMediaPlayer.getVideoSarDen());
+ renderView.setAspectRatio(mCurrentAspectRatio);
+ }
+
+ setRenderView(renderView);
+ break;
+ }
+ case RENDER_SURFACE_VIEW: {
+ SurfaceRenderView renderView = new SurfaceRenderView(getContext());
+ setRenderView(renderView);
+ break;
+ }
+ default:
+ Log.e(TAG, String.format(Locale.getDefault(), "invalid render %d\n", render));
+ break;
+ }
+ }
+
+ /**
+ * Sets video path.
+ *
+ * @param path the path of the video.
+ */
+ public void setVideoPath(String path) {
+ setVideoURI(Uri.parse(path));
+ }
+
+ /**
+ * Sets video URI.
+ *
+ * @param uri the URI of the video.
+ */
+ public void setVideoURI(Uri uri) {
+ setVideoURI(uri, null);
+ }
+
+ /**
+ * Sets video URI using specific headers.
+ *
+ * @param uri the URI of the video.
+ * @param headers the headers for the URI request.
+ * 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.
+ */
+ private void setVideoURI(Uri uri, Map headers) {
+ mUri = uri;
+ mHeaders = headers;
+ mSeekWhenPrepared = 0;
+
+ openVideo();
+ requestLayout();
+ invalidate();
+ }
+
+ public int getVideoWidth(){
+ return mVideoWidth;
+ }
+
+ public int getmVideoHeight(){
+ return mVideoHeight;
+ }
+
+ private String buildResolution(int width, int height, int sarNum, int sarDen) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(width);
+ sb.append(" x ");
+ sb.append(height);
+
+ if (sarNum > 1 || sarDen > 1) {
+ sb.append("[");
+ sb.append(sarNum);
+ sb.append(":");
+ sb.append(sarDen);
+ sb.append("]");
+ }
+
+ return sb.toString();
+ }
+
+ private String buildTimeMilli(long duration) {
+ long total_seconds = duration / 1000;
+ long hours = total_seconds / 3600;
+ long minutes = (total_seconds % 3600) / 60;
+ long seconds = total_seconds % 60;
+
+ if (duration <= 0) {
+ return "--:--";
+ }
+
+ if (hours >= 100) {
+ return String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds);
+ } else if (hours > 0) {
+ return String.format(Locale.US, "%02d:%02d:%02d", hours, minutes, seconds);
+ } else {
+ return String.format(Locale.US, "%02d:%02d", minutes, seconds);
+ }
+ }
+
+ private String buildTrackType(int type) {
+ Context context = getContext();
+ switch (type) {
+ case ITrackInfo.MEDIA_TRACK_TYPE_VIDEO:
+ return context.getString(R.string.TrackType_video);
+ case ITrackInfo.MEDIA_TRACK_TYPE_AUDIO:
+ return context.getString(R.string.TrackType_audio);
+ case ITrackInfo.MEDIA_TRACK_TYPE_SUBTITLE:
+ return context.getString(R.string.TrackType_subtitle);
+ case ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT:
+ return context.getString(R.string.TrackType_timedtext);
+ case ITrackInfo.MEDIA_TRACK_TYPE_METADATA:
+ return context.getString(R.string.TrackType_metadata);
+ case ITrackInfo.MEDIA_TRACK_TYPE_UNKNOWN:
+ default:
+ return context.getString(R.string.TrackType_unknown);
+ }
+ }
+
+ private String buildLanguage(String language) {
+ if (TextUtils.isEmpty(language))
+ return "und";
+ return language;
+ }
+
+ public ITrackInfo[] getTrackInfo() {
+ if (mMediaPlayer == null)
+ return null;
+
+ return mMediaPlayer.getTrackInfo();
+ }
+
+ public void selectTrack(int stream) {
+ MediaPlayerCompat.selectTrack(mMediaPlayer, stream);
+ }
+
+ public void deselectTrack(int stream) {
+ MediaPlayerCompat.deselectTrack(mMediaPlayer, stream);
+ }
+
+ public int getSelectedTrack(int trackType) {
+ return MediaPlayerCompat.getSelectedTrack(mMediaPlayer, trackType);
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/MeasureHelper.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/MeasureHelper.java
new file mode 100644
index 0000000..696f9cd
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/MeasureHelper.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.widget.media;
+
+import android.content.Context;
+import android.view.View;
+
+
+import java.lang.ref.WeakReference;
+
+import tv.danmaku.ijk.media.player.R;
+
+
+public final class MeasureHelper {
+ private WeakReference mWeakView;
+
+ private int mVideoWidth;
+ private int mVideoHeight;
+ private int mVideoSarNum;
+ private int mVideoSarDen;
+
+ private int mVideoRotationDegree;
+
+ private int mMeasuredWidth;
+ private int mMeasuredHeight;
+
+ private int mCurrentAspectRatio = IRenderView.AR_ASPECT_FIT_PARENT;
+
+ public MeasureHelper(View view) {
+ mWeakView = new WeakReference(view);
+ }
+
+ public View getView() {
+ if (mWeakView == null)
+ return null;
+ return mWeakView.get();
+ }
+
+ public void setVideoSize(int videoWidth, int videoHeight) {
+ mVideoWidth = videoWidth;
+ mVideoHeight = videoHeight;
+ }
+
+ public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) {
+ mVideoSarNum = videoSarNum;
+ mVideoSarDen = videoSarDen;
+ }
+
+ public void setVideoRotation(int videoRotationDegree) {
+ mVideoRotationDegree = videoRotationDegree;
+ }
+
+ /**
+ * Must be called by View.onMeasure(int, int)
+ *
+ * @param widthMeasureSpec
+ * @param heightMeasureSpec
+ */
+ public void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ // + MeasureSpec.toString(heightMeasureSpec) + ")");
+ if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) {
+ int tempSpec = widthMeasureSpec;
+ widthMeasureSpec = heightMeasureSpec;
+ heightMeasureSpec = tempSpec;
+ }
+
+ int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec);
+ int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec);
+ if (mCurrentAspectRatio == IRenderView.AR_MATCH_PARENT) {
+ width = widthMeasureSpec;
+ height = heightMeasureSpec;
+ } else if (mVideoWidth > 0 && mVideoHeight > 0) {
+ int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec);
+ int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec);
+ int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec);
+ int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthSpecMode == View.MeasureSpec.AT_MOST && heightSpecMode == View.MeasureSpec.AT_MOST) {
+ float specAspectRatio = (float) widthSpecSize / (float) heightSpecSize;
+ float displayAspectRatio;
+ switch (mCurrentAspectRatio) {
+ case IRenderView.AR_16_9_FIT_PARENT:
+ displayAspectRatio = 16.0f / 9.0f;
+ if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270)
+ displayAspectRatio = 1.0f / displayAspectRatio;
+ break;
+ case IRenderView.AR_4_3_FIT_PARENT:
+ displayAspectRatio = 4.0f / 3.0f;
+ if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270)
+ displayAspectRatio = 1.0f / displayAspectRatio;
+ break;
+ case IRenderView.AR_ASPECT_FIT_PARENT:
+ case IRenderView.AR_ASPECT_FILL_PARENT:
+ case IRenderView.AR_ASPECT_WRAP_CONTENT:
+ default:
+ displayAspectRatio = (float) mVideoWidth / (float) mVideoHeight;
+ if (mVideoSarNum > 0 && mVideoSarDen > 0)
+ displayAspectRatio = displayAspectRatio * mVideoSarNum / mVideoSarDen;
+ break;
+ }
+ boolean shouldBeWider = displayAspectRatio > specAspectRatio;
+
+ switch (mCurrentAspectRatio) {
+ case IRenderView.AR_ASPECT_FIT_PARENT:
+ case IRenderView.AR_16_9_FIT_PARENT:
+ case IRenderView.AR_4_3_FIT_PARENT:
+ if (shouldBeWider) {
+ // too wide, fix width
+ width = widthSpecSize;
+ height = (int) (width / displayAspectRatio);
+ } else {
+ // too high, fix height
+ height = heightSpecSize;
+ width = (int) (height * displayAspectRatio);
+ }
+ break;
+ case IRenderView.AR_ASPECT_FILL_PARENT:
+ if (shouldBeWider) {
+ // not high enough, fix height
+ height = heightSpecSize;
+ width = (int) (height * displayAspectRatio);
+ } else {
+ // not wide enough, fix width
+ width = widthSpecSize;
+ height = (int) (width / displayAspectRatio);
+ }
+ break;
+ case IRenderView.AR_1_1:
+ width = height = 1;
+ break;
+ case IRenderView.AR_ASPECT_WRAP_CONTENT:
+ default:
+ if (shouldBeWider) {
+ // too wide, fix width
+ width = Math.min(mVideoWidth, widthSpecSize);
+ height = (int) (width / displayAspectRatio);
+ } else {
+ // too high, fix height
+ height = Math.min(mVideoHeight, heightSpecSize);
+ width = (int) (height * displayAspectRatio);
+ }
+ break;
+ }
+ } else if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) {
+ // the size is fixed
+ width = widthSpecSize;
+ height = heightSpecSize;
+
+ // for compatibility, we adjust size based on aspect ratio
+ if (mVideoWidth * height < width * mVideoHeight) {
+ //Log.i("@@@", "image too wide, correcting");
+ width = height * mVideoWidth / mVideoHeight;
+ } else if (mVideoWidth * height > width * mVideoHeight) {
+ //Log.i("@@@", "image too tall, correcting");
+ height = width * mVideoHeight / mVideoWidth;
+ }
+ } else if (widthSpecMode == View.MeasureSpec.EXACTLY) {
+ // only the width is fixed, adjust the height to match aspect ratio if possible
+ width = widthSpecSize;
+ height = width * mVideoHeight / mVideoWidth;
+ if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
+ // couldn't match aspect ratio within the constraints
+ height = heightSpecSize;
+ }
+ } else if (heightSpecMode == View.MeasureSpec.EXACTLY) {
+ // only the height is fixed, adjust the width to match aspect ratio if possible
+ height = heightSpecSize;
+ width = height * mVideoWidth / mVideoHeight;
+ if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
+ // couldn't match aspect ratio within the constraints
+ width = widthSpecSize;
+ }
+ } else {
+ // neither the width nor the height are fixed, try to use actual video size
+ width = mVideoWidth;
+ height = mVideoHeight;
+ if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
+ // too tall, decrease both width and height
+ height = heightSpecSize;
+ width = height * mVideoWidth / mVideoHeight;
+ }
+ if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
+ // too wide, decrease both width and height
+ width = widthSpecSize;
+ height = width * mVideoHeight / mVideoWidth;
+ }
+ }
+ } else {
+ // no size yet, just adopt the given spec sizes
+ }
+
+ mMeasuredWidth = width;
+ mMeasuredHeight = height;
+ }
+
+ public int getMeasuredWidth() {
+ return mMeasuredWidth;
+ }
+
+ public int getMeasuredHeight() {
+ return mMeasuredHeight;
+ }
+
+ public void setAspectRatio(int aspectRatio) {
+ mCurrentAspectRatio = aspectRatio;
+ }
+
+
+ public static String getAspectRatioText(Context context, int aspectRatio) {
+ String text;
+ switch (aspectRatio) {
+ case IRenderView.AR_ASPECT_FIT_PARENT:
+ text = context.getString(R.string.VideoView_ar_aspect_fit_parent);
+ break;
+ case IRenderView.AR_ASPECT_FILL_PARENT:
+ text = context.getString(R.string.VideoView_ar_aspect_fill_parent);
+ break;
+ case IRenderView.AR_ASPECT_WRAP_CONTENT:
+ text = context.getString(R.string.VideoView_ar_aspect_wrap_content);
+ break;
+ case IRenderView.AR_MATCH_PARENT:
+ text = context.getString(R.string.VideoView_ar_match_parent);
+ break;
+ case IRenderView.AR_16_9_FIT_PARENT:
+ text = context.getString(R.string.VideoView_ar_16_9_fit_parent);
+ break;
+ case IRenderView.AR_4_3_FIT_PARENT:
+ text = context.getString(R.string.VideoView_ar_4_3_fit_parent);
+ break;
+ default:
+ text = context.getString(R.string.N_A);
+ break;
+ }
+ return text;
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/MediaPlayerCompat.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/MediaPlayerCompat.java
new file mode 100644
index 0000000..f7231a4
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/MediaPlayerCompat.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.widget.media;
+
+import tv.danmaku.ijk.media.player.IMediaPlayer;
+import tv.danmaku.ijk.media.player.IjkMediaPlayer;
+import tv.danmaku.ijk.media.player.MediaPlayerProxy;
+import tv.danmaku.ijk.media.player.TextureMediaPlayer;
+
+public class MediaPlayerCompat {
+ public static String getName(IMediaPlayer mp) {
+ if (mp == null) {
+ return "null";
+ } else if (mp instanceof TextureMediaPlayer) {
+ StringBuilder sb = new StringBuilder("TextureMediaPlayer <");
+ IMediaPlayer internalMediaPlayer = ((TextureMediaPlayer) mp).getInternalMediaPlayer();
+ if (internalMediaPlayer == null) {
+ sb.append("null>");
+ } else {
+ sb.append(internalMediaPlayer.getClass().getSimpleName());
+ sb.append(">");
+ }
+ return sb.toString();
+ } else {
+ return mp.getClass().getSimpleName();
+ }
+ }
+
+ public static IjkMediaPlayer getIjkMediaPlayer(IMediaPlayer mp) {
+ IjkMediaPlayer ijkMediaPlayer = null;
+ if (mp == null) {
+ return null;
+ } if (mp instanceof IjkMediaPlayer) {
+ ijkMediaPlayer = (IjkMediaPlayer) mp;
+ } else if (mp instanceof MediaPlayerProxy && ((MediaPlayerProxy) mp).getInternalMediaPlayer() instanceof IjkMediaPlayer) {
+ ijkMediaPlayer = (IjkMediaPlayer) ((MediaPlayerProxy) mp).getInternalMediaPlayer();
+ }
+ return ijkMediaPlayer;
+ }
+
+ public static void selectTrack(IMediaPlayer mp, int stream) {
+ IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp);
+ if (ijkMediaPlayer == null)
+ return;
+ ijkMediaPlayer.selectTrack(stream);
+ }
+
+ public static void deselectTrack(IMediaPlayer mp, int stream) {
+ IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp);
+ if (ijkMediaPlayer == null)
+ return;
+ ijkMediaPlayer.deselectTrack(stream);
+ }
+
+ public static int getSelectedTrack(IMediaPlayer mp, int trackType) {
+ IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp);
+ if (ijkMediaPlayer == null)
+ return -1;
+ return ijkMediaPlayer.getSelectedTrack(trackType);
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/SurfaceRenderView.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/SurfaceRenderView.java
new file mode 100644
index 0000000..2d5b917
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/SurfaceRenderView.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.widget.media;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.lang.ref.WeakReference;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import tv.danmaku.ijk.media.player.IMediaPlayer;
+import tv.danmaku.ijk.media.player.ISurfaceTextureHolder;
+import tv.danmaku.ijk.media.widget.media.IRenderView;
+
+public class SurfaceRenderView extends SurfaceView implements IRenderView {
+ private MeasureHelper mMeasureHelper;
+
+ public SurfaceRenderView(Context context) {
+ super(context);
+ initView(context);
+ }
+
+ public SurfaceRenderView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initView(context);
+ }
+
+ public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initView(context);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ initView(context);
+ }
+
+ private void initView(Context context) {
+ mMeasureHelper = new MeasureHelper(this);
+ mSurfaceCallback = new SurfaceCallback(this);
+ getHolder().addCallback(mSurfaceCallback);
+ //noinspection deprecation
+ getHolder().setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
+ }
+
+ @Override
+ public View getView() {
+ return this;
+ }
+
+ @Override
+ public boolean shouldWaitForResize() {
+ return true;
+ }
+
+ //--------------------
+ // Layout & Measure
+ //--------------------
+ @Override
+ public void setVideoSize(int videoWidth, int videoHeight) {
+ if (videoWidth > 0 && videoHeight > 0) {
+ mMeasureHelper.setVideoSize(videoWidth, videoHeight);
+ getHolder().setFixedSize(videoWidth, videoHeight);
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) {
+ if (videoSarNum > 0 && videoSarDen > 0) {
+ mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen);
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void setVideoRotation(int degree) {
+ Log.e("", "SurfaceView doesn't support rotation (" + degree + ")!\n");
+ }
+
+ @Override
+ public void setAspectRatio(int aspectRatio) {
+ mMeasureHelper.setAspectRatio(aspectRatio);
+ requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec);
+ setMeasuredDimension(mMeasureHelper.getMeasuredWidth(), mMeasureHelper.getMeasuredHeight());
+ }
+
+ //--------------------
+ // SurfaceViewHolder
+ //--------------------
+
+ private static final class InternalSurfaceHolder implements IRenderView.ISurfaceHolder {
+ private SurfaceRenderView mSurfaceView;
+ private SurfaceHolder mSurfaceHolder;
+
+ public InternalSurfaceHolder( SurfaceRenderView surfaceView,
+ SurfaceHolder surfaceHolder) {
+ mSurfaceView = surfaceView;
+ mSurfaceHolder = surfaceHolder;
+ }
+
+ public void bindToMediaPlayer(IMediaPlayer mp) {
+ if (mp != null) {
+ if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) &&
+ (mp instanceof ISurfaceTextureHolder)) {
+ ISurfaceTextureHolder textureHolder = (ISurfaceTextureHolder) mp;
+ textureHolder.setSurfaceTexture(null);
+ }
+ mp.setDisplay(mSurfaceHolder);
+ }
+ }
+
+ @Override
+ public IRenderView getRenderView() {
+ return mSurfaceView;
+ }
+
+ @Override
+ public SurfaceHolder getSurfaceHolder() {
+ return mSurfaceHolder;
+ }
+
+ @Override
+ public SurfaceTexture getSurfaceTexture() {
+ return null;
+ }
+
+ @Override
+ public Surface openSurface() {
+ if (mSurfaceHolder == null)
+ return null;
+ return mSurfaceHolder.getSurface();
+ }
+ }
+
+ //-------------------------
+ // SurfaceHolder.Callback
+ //-------------------------
+
+ @Override
+ public void addRenderCallback(IRenderCallback callback) {
+ mSurfaceCallback.addRenderCallback(callback);
+ }
+
+ @Override
+ public void removeRenderCallback(IRenderCallback callback) {
+ mSurfaceCallback.removeRenderCallback(callback);
+ }
+
+ private SurfaceCallback mSurfaceCallback;
+
+ private static final class SurfaceCallback implements SurfaceHolder.Callback {
+ private SurfaceHolder mSurfaceHolder;
+ private boolean mIsFormatChanged;
+ private int mFormat;
+ private int mWidth;
+ private int mHeight;
+
+ private WeakReference mWeakSurfaceView;
+ private Map mRenderCallbackMap = new ConcurrentHashMap();
+
+ public SurfaceCallback(SurfaceRenderView surfaceView) {
+ mWeakSurfaceView = new WeakReference(surfaceView);
+ }
+
+ public void addRenderCallback(IRenderCallback callback) {
+ mRenderCallbackMap.put(callback, callback);
+
+ ISurfaceHolder surfaceHolder = null;
+ if (mSurfaceHolder != null) {
+ if (surfaceHolder == null)
+ surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
+ callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight);
+ }
+
+ if (mIsFormatChanged) {
+ if (surfaceHolder == null)
+ surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
+ callback.onSurfaceChanged(surfaceHolder, mFormat, mWidth, mHeight);
+ }
+ }
+
+ public void removeRenderCallback(IRenderCallback callback) {
+ mRenderCallbackMap.remove(callback);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mSurfaceHolder = holder;
+ mIsFormatChanged = false;
+ mFormat = 0;
+ mWidth = 0;
+ mHeight = 0;
+
+ ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
+ for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
+ renderCallback.onSurfaceCreated(surfaceHolder, 0, 0);
+ }
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ mSurfaceHolder = null;
+ mIsFormatChanged = false;
+ mFormat = 0;
+ mWidth = 0;
+ mHeight = 0;
+
+ ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
+ for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
+ renderCallback.onSurfaceDestroyed(surfaceHolder);
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format,
+ int width, int height) {
+ mSurfaceHolder = holder;
+ mIsFormatChanged = true;
+ mFormat = format;
+ mWidth = width;
+ mHeight = height;
+
+ // mMeasureHelper.setVideoSize(width, height);
+
+ ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
+ for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
+ renderCallback.onSurfaceChanged(surfaceHolder, format, width, height);
+ }
+ }
+ }
+
+ //--------------------
+ // Accessibility
+ //--------------------
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setClassName(SurfaceRenderView.class.getName());
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ info.setClassName(SurfaceRenderView.class.getName());
+ }
+ }
+}
diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/TextureRenderView.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/TextureRenderView.java
new file mode 100644
index 0000000..fee9005
--- /dev/null
+++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/widget/media/TextureRenderView.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.widget.media;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.TextureView;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.lang.ref.WeakReference;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import tv.danmaku.ijk.media.player.IMediaPlayer;
+import tv.danmaku.ijk.media.player.ISurfaceTextureHolder;
+import tv.danmaku.ijk.media.player.ISurfaceTextureHost;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class TextureRenderView extends TextureView implements IRenderView {
+ private static final String TAG = "TextureRenderView";
+ private MeasureHelper mMeasureHelper;
+
+ public TextureRenderView(Context context) {
+ super(context);
+ initView(context);
+ }
+
+ public TextureRenderView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initView(context);
+ }
+
+ public TextureRenderView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initView(context);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public TextureRenderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ initView(context);
+ }
+
+ private void initView(Context context) {
+ mMeasureHelper = new MeasureHelper(this);
+ mSurfaceCallback = new SurfaceCallback(this);
+ setSurfaceTextureListener(mSurfaceCallback);
+ }
+
+ @Override
+ public View getView() {
+ return this;
+ }
+
+ @Override
+ public boolean shouldWaitForResize() {
+ return false;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ mSurfaceCallback.willDetachFromWindow();
+ super.onDetachedFromWindow();
+ mSurfaceCallback.didDetachFromWindow();
+ }
+
+ //--------------------
+ // Layout & Measure
+ //--------------------
+ @Override
+ public void setVideoSize(int videoWidth, int videoHeight) {
+ if (videoWidth > 0 && videoHeight > 0) {
+ mMeasureHelper.setVideoSize(videoWidth, videoHeight);
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) {
+ if (videoSarNum > 0 && videoSarDen > 0) {
+ mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen);
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void setVideoRotation(int degree) {
+ mMeasureHelper.setVideoRotation(degree);
+ setRotation(degree);
+ }
+
+ @Override
+ public void setAspectRatio(int aspectRatio) {
+ mMeasureHelper.setAspectRatio(aspectRatio);
+ requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec);
+ setMeasuredDimension(mMeasureHelper.getMeasuredWidth(), mMeasureHelper.getMeasuredHeight());
+ }
+
+ //--------------------
+ // TextureViewHolder
+ //--------------------
+
+ public IRenderView.ISurfaceHolder getSurfaceHolder() {
+ return new InternalSurfaceHolder(this, mSurfaceCallback.mSurfaceTexture, mSurfaceCallback);
+ }
+
+ private static final class InternalSurfaceHolder implements IRenderView.ISurfaceHolder {
+ private TextureRenderView mTextureView;
+ private SurfaceTexture mSurfaceTexture;
+ private ISurfaceTextureHost mSurfaceTextureHost;
+
+ public InternalSurfaceHolder( TextureRenderView textureView,
+ SurfaceTexture surfaceTexture,
+ ISurfaceTextureHost surfaceTextureHost) {
+ mTextureView = textureView;
+ mSurfaceTexture = surfaceTexture;
+ mSurfaceTextureHost = surfaceTextureHost;
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ public void bindToMediaPlayer(IMediaPlayer mp) {
+ if (mp == null)
+ return;
+
+ if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) &&
+ (mp instanceof ISurfaceTextureHolder)) {
+ ISurfaceTextureHolder textureHolder = (ISurfaceTextureHolder) mp;
+ mTextureView.mSurfaceCallback.setOwnSurfaceTexture(false);
+
+ SurfaceTexture surfaceTexture = textureHolder.getSurfaceTexture();
+ if (surfaceTexture != null) {
+ mTextureView.setSurfaceTexture(surfaceTexture);
+ } else {
+ textureHolder.setSurfaceTexture(mSurfaceTexture);
+ textureHolder.setSurfaceTextureHost(mTextureView.mSurfaceCallback);
+ }
+ } else {
+ mp.setSurface(openSurface());
+ }
+ }
+
+
+ @Override
+ public IRenderView getRenderView() {
+ return mTextureView;
+ }
+
+
+ @Override
+ public SurfaceHolder getSurfaceHolder() {
+ return null;
+ }
+
+
+ @Override
+ public SurfaceTexture getSurfaceTexture() {
+ return mSurfaceTexture;
+ }
+
+
+ @Override
+ public Surface openSurface() {
+ if (mSurfaceTexture == null)
+ return null;
+ return new Surface(mSurfaceTexture);
+ }
+ }
+
+ //-------------------------
+ // SurfaceHolder.Callback
+ //-------------------------
+
+ @Override
+ public void addRenderCallback(IRenderCallback callback) {
+ mSurfaceCallback.addRenderCallback(callback);
+ }
+
+ @Override
+ public void removeRenderCallback(IRenderCallback callback) {
+ mSurfaceCallback.removeRenderCallback(callback);
+ }
+
+ private SurfaceCallback mSurfaceCallback;
+
+ private static final class SurfaceCallback implements SurfaceTextureListener, ISurfaceTextureHost {
+ private SurfaceTexture mSurfaceTexture;
+ private boolean mIsFormatChanged;
+ private int mWidth;
+ private int mHeight;
+
+ private boolean mOwnSurfaceTexture = true;
+ private boolean mWillDetachFromWindow = false;
+ private boolean mDidDetachFromWindow = false;
+
+ private WeakReference mWeakRenderView;
+ private Map mRenderCallbackMap = new ConcurrentHashMap();
+
+ public SurfaceCallback( TextureRenderView renderView) {
+ mWeakRenderView = new WeakReference(renderView);
+ }
+
+ public void setOwnSurfaceTexture(boolean ownSurfaceTexture) {
+ mOwnSurfaceTexture = ownSurfaceTexture;
+ }
+
+ public void addRenderCallback( IRenderCallback callback) {
+ mRenderCallbackMap.put(callback, callback);
+
+ ISurfaceHolder surfaceHolder = null;
+ if (mSurfaceTexture != null) {
+ if (surfaceHolder == null)
+ surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), mSurfaceTexture, this);
+ callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight);
+ }
+
+ if (mIsFormatChanged) {
+ if (surfaceHolder == null)
+ surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), mSurfaceTexture, this);
+ callback.onSurfaceChanged(surfaceHolder, 0, mWidth, mHeight);
+ }
+ }
+
+ public void removeRenderCallback( IRenderCallback callback) {
+ mRenderCallbackMap.remove(callback);
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+ mSurfaceTexture = surface;
+ mIsFormatChanged = false;
+ mWidth = 0;
+ mHeight = 0;
+
+ ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this);
+ for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
+ renderCallback.onSurfaceCreated(surfaceHolder, 0, 0);
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ mSurfaceTexture = surface;
+ mIsFormatChanged = true;
+ mWidth = width;
+ mHeight = height;
+
+ ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this);
+ for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
+ renderCallback.onSurfaceChanged(surfaceHolder, 0, width, height);
+ }
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ mSurfaceTexture = surface;
+ mIsFormatChanged = false;
+ mWidth = 0;
+ mHeight = 0;
+
+ ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this);
+ for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
+ renderCallback.onSurfaceDestroyed(surfaceHolder);
+ }
+
+ Log.d(TAG, "onSurfaceTextureDestroyed: destroy: " + mOwnSurfaceTexture);
+ return mOwnSurfaceTexture;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ }
+
+ //-------------------------
+ // ISurfaceTextureHost
+ //-------------------------
+
+ @Override
+ public void releaseSurfaceTexture(SurfaceTexture surfaceTexture) {
+ if (surfaceTexture == null) {
+ Log.d(TAG, "releaseSurfaceTexture: null");
+ } else if (mDidDetachFromWindow) {
+ if (surfaceTexture != mSurfaceTexture) {
+ Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release different SurfaceTexture");
+ surfaceTexture.release();
+ } else if (!mOwnSurfaceTexture) {
+ Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release detached SurfaceTexture");
+ surfaceTexture.release();
+ } else {
+ Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): already released by TextureView");
+ }
+ } else if (mWillDetachFromWindow) {
+ if (surfaceTexture != mSurfaceTexture) {
+ Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): release different SurfaceTexture");
+ surfaceTexture.release();
+ } else if (!mOwnSurfaceTexture) {
+ Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): re-attach SurfaceTexture to TextureView");
+ setOwnSurfaceTexture(true);
+ } else {
+ Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): will released by TextureView");
+ }
+ } else {
+ if (surfaceTexture != mSurfaceTexture) {
+ Log.d(TAG, "releaseSurfaceTexture: alive: release different SurfaceTexture");
+ surfaceTexture.release();
+ } else if (!mOwnSurfaceTexture) {
+ Log.d(TAG, "releaseSurfaceTexture: alive: re-attach SurfaceTexture to TextureView");
+ setOwnSurfaceTexture(true);
+ } else {
+ Log.d(TAG, "releaseSurfaceTexture: alive: will released by TextureView");
+ }
+ }
+ }
+
+ public void willDetachFromWindow() {
+ Log.d(TAG, "willDetachFromWindow()");
+ mWillDetachFromWindow = true;
+ }
+
+ public void didDetachFromWindow() {
+ Log.d(TAG, "didDetachFromWindow()");
+ mDidDetachFromWindow = true;
+ }
+ }
+
+ //--------------------
+ // Accessibility
+ //--------------------
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setClassName(TextureRenderView.class.getName());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(TextureRenderView.class.getName());
+ }
+}
diff --git a/ijkplayer-java/src/main/project.properties b/ijkplayer-java/src/main/project.properties
new file mode 100644
index 0000000..362a0a3
--- /dev/null
+++ b/ijkplayer-java/src/main/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-22
+android.library=true
diff --git a/ijkplayer-java/src/main/res/values/strings.xml b/ijkplayer-java/src/main/res/values/strings.xml
new file mode 100644
index 0000000..f7095ad
--- /dev/null
+++ b/ijkplayer-java/src/main/res/values/strings.xml
@@ -0,0 +1,60 @@
+
+
+
+
+ N/A
+
+ Video
+ Audio
+ Subtitle
+ Timed text
+ Meta data
+ Unknown
+
+ Aspect / Fit parent
+ Aspect / Fill parent
+ Aspect / Wrap content
+ Free / Fill parent
+ 16:9 / Fit parent
+ 4:3 / Fit parent
+
+ Render: None
+ Render: SurfaceView
+ Render: TextureView
+
+
+
+ pref.enable_background_play
+
+ pref.player
+
+
+
+ pref.using_media_codec
+
+ pref.using_media_codec_auto_rotate
+
+ pref.media_codec_handle_resolution_change
+
+ pref.pixel_format
+
+
+
+ pref.using_opensl_es
+
+
+
+ pref.enable_no_view
+
+ pref.enable_surface_view
+
+ pref.enable_texture_view
+
+ pref.enable_detached_surface_texture
+
+
+ pref.using_mediadatasource
+
+
+
+
\ No newline at end of file
diff --git a/myapplication/build.gradle b/myapplication/build.gradle
index 50b8189..d26a4b0 100644
--- a/myapplication/build.gradle
+++ b/myapplication/build.gradle
@@ -3,7 +3,6 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 31
-
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
@@ -30,6 +29,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
+
+ viewBinding {
+ enabled = true
+ }
}
@@ -51,7 +54,9 @@ dependencies {
}
implementation 'io.reactivex.rxjava2:rxjava:2.1.6'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
+ implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-reactivestreams:2.4.1'
+ implementation project(path: ':ijkplayer-java')
annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.0.0'
}
diff --git a/myapplication/src/main/AndroidManifest.xml b/myapplication/src/main/AndroidManifest.xml
index 09c024f..55ffcd9 100644
--- a/myapplication/src/main/AndroidManifest.xml
+++ b/myapplication/src/main/AndroidManifest.xml
@@ -50,5 +50,11 @@
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
+
+
\ No newline at end of file
diff --git a/myapplication/src/main/java/com/example/myapplication/MainActivity.java b/myapplication/src/main/java/com/example/myapplication/MainActivity.java
index ae4b187..3059a19 100644
--- a/myapplication/src/main/java/com/example/myapplication/MainActivity.java
+++ b/myapplication/src/main/java/com/example/myapplication/MainActivity.java
@@ -1,16 +1,22 @@
package com.example.myapplication;
import androidx.lifecycle.Observer;
+
+import android.Manifest;
+import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.preference.PreferenceManager;
+
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.appcompat.app.AppCompatActivity;
+
import android.view.TextureView;
import android.view.View;
import android.widget.CheckBox;
@@ -18,6 +24,8 @@ import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Toast;
+import com.example.myapplication.player.ProVideoActivity;
+
import org.easydarwin.push.MediaStream;
import io.reactivex.Single;
@@ -49,6 +57,7 @@ public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
CheckBox hevc_enable = findViewById(R.id.enable_265);
hevc_enable.setChecked(PreferenceManager.getDefaultSharedPreferences(this).getBoolean("try_265_encode", false));
@@ -105,10 +114,10 @@ public class MainActivity extends AppCompatActivity {
pushingStateText.append("\n");
if ("avc".equals(pushingState.videoCodec)) {
pushingStateText.append("视频编码方式:" + "H264硬编码");
- }else if ("hevc".equals(pushingState.videoCodec)) {
- pushingStateText.append("视频编码方式:" + "H265硬编码");
- }else if ("x264".equals(pushingState.videoCodec)) {
- pushingStateText.append("视频编码方式:" + "x264");
+ } else if ("hevc".equals(pushingState.videoCodec)) {
+ pushingStateText.append("视频编码方式:" + "H265硬编码");
+ } else if ("x264".equals(pushingState.videoCodec)) {
+ pushingStateText.append("视频编码方式:" + "x264");
}
}
@@ -232,15 +241,18 @@ public class MainActivity extends AppCompatActivity {
}
public void onUVCCamera(View view) {
- Intent intent = new Intent(this, UVCActivity.class);
+// Intent intent = new Intent(this, UVCActivity.class);
+ Intent intent = new Intent(this, ProVideoActivity.class);
+ String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/mvtest.mp4";
+ intent.putExtra("videoPath", path);
startActivity(intent);
}
public void OnSaveRecord(View view) {
- if(mediaStream.isRecording()){
+ if (mediaStream.isRecording()) {
mediaStream.stopRecord();
- }else{
- mediaStream.startRecord(this.getCacheDir().getAbsolutePath(), 10*1000);
+ } else {
+ mediaStream.startRecord(this.getCacheDir().getAbsolutePath(), 10 * 1000);
}
}
}
diff --git a/myapplication/src/main/java/com/example/myapplication/player/ProVideoActivity.java b/myapplication/src/main/java/com/example/myapplication/player/ProVideoActivity.java
new file mode 100644
index 0000000..12df891
--- /dev/null
+++ b/myapplication/src/main/java/com/example/myapplication/player/ProVideoActivity.java
@@ -0,0 +1,470 @@
+package com.example.myapplication.player;
+
+/*
+ * Copyright (C) 2015 Zhang Rui
+ *
+ * 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.
+ */
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
+import android.Manifest;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.ViewConfigurationCompat;
+
+import com.bumptech.glide.Glide;
+import com.example.myapplication.BuildConfig;
+import com.example.myapplication.databinding.ActivityMainProBinding;
+import com.example.myapplication.player.utils.FileUtil;
+import com.example.myapplication.player.utils.SPUtil;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import tv.danmaku.ijk.media.player.IMediaPlayer;
+import tv.danmaku.ijk.media.player.IjkMediaPlayer;
+
+public class ProVideoActivity extends AppCompatActivity {
+ private static final String TAG = "ProVideoActivity";
+
+ public static final int REQUEST_WRITE_STORAGE = 111;
+
+ private String mVideoPath;
+ private Uri mVideoUri;
+
+ private ActivityMainProBinding mBinding;
+ private ProVideoView mVideoView; // 播放器View
+ private View mProgress;
+
+ private GestureDetector detector;
+
+ private VideoControllerView mediaController;
+ private MediaScannerConnection mScanner;
+
+ private Runnable mSpeedCalcTask;
+
+ private int mMode; // 画面模式
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ View decorView = getWindow().getDecorView();
+ decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ getWindow().setStatusBarColor(Color.TRANSPARENT);
+ }
+
+ mBinding = ActivityMainProBinding.inflate(getLayoutInflater());
+ setContentView(mBinding.getRoot());
+
+ // handle arguments
+ mVideoPath = getIntent().getStringExtra("videoPath");
+
+ String mSnapPath = getIntent().getStringExtra("snapPath");
+ if (TextUtils.isEmpty(mSnapPath)) {
+ Glide.with(this).load(mSnapPath).into(mBinding.surfaceCover);
+ ViewCompat.setTransitionName(mBinding.surfaceCover, "snapCover");
+ }
+
+ Intent intent = getIntent();
+ String intentAction = intent.getAction();
+ if (!TextUtils.isEmpty(intentAction)) {
+ if (intentAction.equals(Intent.ACTION_VIEW)) {
+ mVideoPath = intent.getDataString();
+ } else if (intentAction.equals(Intent.ACTION_SEND)) {
+ mVideoUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ String scheme = mVideoUri.getScheme();
+
+ if (TextUtils.isEmpty(scheme)) {
+ Log.e(TAG, "Null unknown scheme\n");
+ finish();
+ return;
+ }
+
+ if (scheme.equals(ContentResolver.SCHEME_ANDROID_RESOURCE)) {
+ mVideoPath = mVideoUri.getPath();
+ } else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
+ Log.e(TAG, "Can not resolve content below Android-ICS\n");
+ finish();
+ return;
+ } else {
+ Log.e(TAG, "Unknown scheme " + scheme + "\n");
+ finish();
+ return;
+ }
+ }
+ }
+ }
+
+ SPUtil.setDefaultParams(this);
+
+ if (BuildConfig.DEBUG) {
+ IjkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG);// init player
+ }
+
+ mediaController = new VideoControllerView(this);
+ mediaController.setMediaPlayer(mBinding.videoView);
+ mVideoView = mBinding.videoView;
+ mVideoView.setMediaController(mediaController);
+
+ mProgress = mBinding.progress;
+ mVideoView.setOnInfoListener(new IMediaPlayer.OnInfoListener() {
+ @Override
+ public boolean onInfo(IMediaPlayer iMediaPlayer, int arg1, int arg2) {
+ switch (arg1) {
+ case IMediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING:
+ Log.i(TAG, "MEDIA_INFO_VIDEO_TRACK_LAGGING");
+ break;
+ case IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:
+ Log.i(TAG, "MEDIA_INFO_VIDEO_RENDERING_START");
+ mProgress.setVisibility(View.GONE);
+ mBinding.surfaceCover.setVisibility(View.GONE);
+ mBinding.playerContainer.setVisibility(View.GONE);
+ mBinding.videoView.setVisibility(View.VISIBLE);
+ mBinding.videoView2.setVisibility(View.VISIBLE);
+
+ // 快照
+ File file = FileUtil.getSnapshotFile(mVideoPath);
+ mVideoView.takePicture(file.getPath());
+ break;
+ case IMediaPlayer.MEDIA_INFO_BUFFERING_START:
+ Log.i(TAG, "MEDIA_INFO_BUFFERING_START");
+ break;
+ case IMediaPlayer.MEDIA_INFO_BUFFERING_END:
+ Log.i(TAG, "MEDIA_INFO_BUFFERING_END");
+ break;
+ case IMediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH:
+ Log.i(TAG, "MEDIA_INFO_NETWORK_BANDWIDTH");
+ break;
+ case IMediaPlayer.MEDIA_INFO_BAD_INTERLEAVING:
+ Log.i(TAG, "MEDIA_INFO_BAD_INTERLEAVING");
+ break;
+ case IMediaPlayer.MEDIA_INFO_NOT_SEEKABLE:
+ Log.i(TAG, "MEDIA_INFO_NOT_SEEKABLE");
+ break;
+ case IMediaPlayer.MEDIA_INFO_METADATA_UPDATE:
+ Log.i(TAG, "MEDIA_INFO_METADATA_UPDATE");
+ break;
+ case IMediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE:
+ Log.i(TAG, "MEDIA_INFO_UNSUPPORTED_SUBTITLE");
+ break;
+ case IMediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT:
+ Log.i(TAG, "MEDIA_INFO_SUBTITLE_TIMED_OUT");
+ break;
+ case IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED:
+ Log.i(TAG, "MEDIA_INFO_VIDEO_ROTATION_CHANGED");
+ break;
+ case IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START:
+ Log.i(TAG, "MEDIA_INFO_AUDIO_RENDERING_START");
+ break;
+ }
+
+ return false;
+ }
+ });
+
+ mVideoView.setOnErrorListener(new IMediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(IMediaPlayer iMediaPlayer, int i, int i1) {
+ Log.i(TAG, "播放错误");
+ mBinding.videoView.setVisibility(View.GONE);
+ mBinding.videoView2.setVisibility(View.GONE);
+ mProgress.setVisibility(View.GONE);
+ mBinding.playerContainer.setVisibility(View.VISIBLE);
+
+ mVideoView.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mVideoView.reStart();
+ }
+ }, 5000);
+
+ return true;
+ }
+ });
+
+ mVideoView.setOnCompletionListener(new IMediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(IMediaPlayer iMediaPlayer) {
+ Log.i(TAG, "播放完成");
+ mProgress.setVisibility(View.GONE);
+
+ if (mVideoPath.toLowerCase().startsWith("rtsp") ||
+ mVideoPath.toLowerCase().startsWith("rtmp") ||
+ mVideoPath.toLowerCase().startsWith("http")) {
+ mVideoView.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mVideoView.reStart();
+ }
+ }, 5000);
+ }
+ }
+ });
+
+ mVideoView.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(IMediaPlayer iMediaPlayer) {
+ Log.i(TAG, String.format("onPrepared"));
+ }
+ });
+
+ if (mVideoPath != null) {
+ mVideoView.setVideoPath(mVideoPath);
+ } else if (mVideoUri != null) {
+ mVideoView.setVideoURI(mVideoUri);
+ } else {
+ Log.e(TAG, "Null Data Source\n");
+ finish();
+ return;
+ }
+
+ mVideoView.start();
+
+ GestureDetector.SimpleOnGestureListener listener = new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+// if (!isLandscape() && mMode == 3) {
+// if (mFullScreenMode) {
+// mMode = mVideoView.toggleAspectRatio();
+// mBinding.playerContainer.setVisibility(View.VISIBLE);
+// mFullScreenMode = false;
+// } else {
+// mFullScreenMode = true;
+// mBinding.playerContainer.setVisibility(View.GONE);
+// }
+// } else {
+// mMode = mVideoView.toggleAspectRatio();
+// }
+
+ if (mVideoView.isInPlaybackState()) {
+ mVideoView.toggleMediaControlsVisibility();
+ return true;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+// setRequestedOrientation(isLandscape() ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ return true;
+ }
+ };
+
+ detector = new GestureDetector(this, listener);
+
+ mVideoView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ detector.onTouchEvent(event);
+
+ return true;
+ }
+ });
+
+ mSpeedCalcTask = new Runnable() {
+ private long mReceivedBytes;
+
+ @Override
+ public void run() {
+ long l = mVideoView.getReceivedBytes();
+ long received = l - mReceivedBytes;
+ mReceivedBytes = l;
+
+ mBinding.loadingSpeed.setText(String.format("%3.01fKB/s", received * 1.0f / 1024));
+
+ if (mProgress.getVisibility() == View.VISIBLE){
+ mVideoView.postDelayed(this,1000);
+ }
+ }
+ };
+
+ mVideoView.post(mSpeedCalcTask);
+
+ if (BuildConfig.DEBUG) {
+ mBinding.videoView2.setVideoPath("rtmp://13088.liveplay.myqcloud.com/live/13088_65829b3d3e");
+ mBinding.videoView2.setShowing(false);
+ mBinding.videoView2.start();
+ }
+ }
+
+// @Override
+// protected void onResume() {
+// super.onResume();
+//
+// if (mVideoView != null) {
+// mVideoView.postDelayed(new Runnable() {
+// @Override
+// public void run() {
+// mVideoView.reStart();
+// }
+// }, 5000);
+// }
+// }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ mVideoView.stopPlayback();
+
+ if (BuildConfig.DEBUG) {
+ mBinding.videoView2.stopPlayback();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ if (mScanner != null) {
+ mScanner.disconnect();
+ mScanner = null;
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (REQUEST_WRITE_STORAGE == requestCode){
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ doTakePicture();
+ }
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ LinearLayout container = mBinding.playerContainer;
+
+ if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ setNavVisibility(false);
+ // 横屏情况 播放窗口横着排开
+ container.setOrientation(LinearLayout.HORIZONTAL);
+ } else {
+ // 竖屏,取消全屏状态
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ setNavVisibility(true);
+ // 竖屏情况 播放窗口竖着排开
+ container.setOrientation(LinearLayout.VERTICAL);
+ }
+ }
+
+ public boolean isLandscape() {
+ int orientation = getResources().getConfiguration().orientation;
+ return orientation == ORIENTATION_LANDSCAPE;
+ }
+
+ public void setNavVisibility(boolean visible) {
+ if (!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(this))) {
+ int newVis = View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+
+ if (!visible) {
+ // } else {
+ // newVis &= ~(View.SYSTEM_UI_FLAG_LOW_PROFILE |
+ // View.SYSTEM_UI_FLAG_FULLSCREEN |
+ // View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+ newVis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE;
+ }
+
+ // If we are now visible, schedule a timer for us to go invisible. Set the new desired visibility.
+ getWindow().getDecorView().setSystemUiVisibility(newVis);
+ }
+ }
+
+ public void onChangeOrientation(View view) {
+ setRequestedOrientation(isLandscape() ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ }
+
+ public void onChangePlayMode(View view) {
+ mMode = mVideoView.toggleAspectRatio();
+ Log.i(TAG, "画面模式:" + mMode);
+ }
+
+ /*
+ * 截图
+ * */
+ public void onTakePicture(View view) {
+ if (mVideoView.isInPlaybackState()) {
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_STORAGE);
+ } else {
+ doTakePicture();
+ }
+ }
+ }
+
+ private void doTakePicture() {
+ File file = new File(FileUtil.getPicturePath());
+ file.mkdirs();
+
+ file = new File(file, "pic_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".jpg");
+ final String picture = mVideoView.takePicture(file.getPath());
+
+ if (!TextUtils.isEmpty(picture)) {
+ Toast.makeText(ProVideoActivity.this,"图片已保存", Toast.LENGTH_SHORT).show();
+
+ if (mScanner == null) {
+ MediaScannerConnection connection = new MediaScannerConnection(ProVideoActivity.this, new MediaScannerConnection.MediaScannerConnectionClient() {
+ public void onMediaScannerConnected() {
+ mScanner.scanFile(picture, "image/jpeg");
+ }
+
+ public void onScanCompleted(String path1, Uri uri) {
+
+ }
+ });
+
+ try {
+ connection.connect();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ mScanner = connection;
+ } else {
+ mScanner.scanFile(picture, "image/jpeg");
+ }
+ }
+ }
+}
diff --git a/myapplication/src/main/java/com/example/myapplication/player/ProVideoView.java b/myapplication/src/main/java/com/example/myapplication/player/ProVideoView.java
new file mode 100644
index 0000000..59143c1
--- /dev/null
+++ b/myapplication/src/main/java/com/example/myapplication/player/ProVideoView.java
@@ -0,0 +1,226 @@
+package com.example.myapplication.player;
+
+import static tv.danmaku.ijk.media.player.IjkMediaPlayer.native_active_days;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+
+import androidx.core.app.ActivityCompat;
+
+import com.example.myapplication.player.utils.FileUtil;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import tv.danmaku.ijk.media.player.IjkMediaPlayer;
+import tv.danmaku.ijk.media.widget.media.IjkVideoView;
+
+/**
+ * 播放器
+ *
+ * Created by apple on 2017/2/11.
+ */
+public class ProVideoView extends IjkVideoView implements VideoControllerView.FullScreenAbleMediaPlayerControl {
+
+ private String mRecordPath;
+
+ public ProVideoView(Context context) {
+ super(context);
+ }
+
+ public ProVideoView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ProVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public ProVideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public static void setKey(String key) {
+ Player_KEY = key;
+ }
+
+ public static long getActiveDays(Context context, String key) {
+ return native_active_days(context, key);
+ }
+
+ /**
+ * ================ super override ================
+ */
+
+ public void startRecord(String path, int seconds) {
+ if (mMediaPlayer == null) {
+ return;
+ }
+
+ super.startRecord(path, seconds);
+ mRecordPath = path;
+ }
+
+ public void stopRecord() {
+ if (mMediaPlayer == null) {
+ return;
+ }
+
+ super.stopRecord();
+ mRecordPath = null;
+ }
+
+ /**
+ * ================ FullScreenAbleMediaPlayerControl ================
+ */
+
+ @Override
+ public boolean isFullScreen() {
+ if (getContext() instanceof ProVideoActivity) {
+ ProVideoActivity pro = (ProVideoActivity) getContext();
+ return pro.isLandscape();
+ }
+
+ return false;
+ }
+
+ @Override
+ public void toggleFullScreen() {
+ if (getContext() instanceof ProVideoActivity) {
+ ProVideoActivity pro = (ProVideoActivity) getContext();
+ pro.onChangeOrientation(null);
+ }
+ }
+
+ @Override
+ public boolean recordEnable() {
+ Uri uri = mUri;
+
+ if (uri == null)
+ return false;
+
+ if (uri.getScheme() == null)
+ return false;
+
+ return !uri.getScheme().equals("file");
+ }
+
+ @Override
+ public boolean speedCtrlEnable() {
+ Uri uri = mUri;
+
+ if (uri == null)
+ return false;
+
+ if (uri.getScheme() == null)
+ return true;
+
+ return uri.getScheme().equals("file");
+ }
+
+ @Override
+ public boolean isRecording() {
+ if (mMediaPlayer == null) {
+ return false;
+ }
+
+ return !TextUtils.isEmpty(mRecordPath);
+ }
+
+ @Override
+ public void reStart() {
+ super.reStart();
+ if (mRecordPath != null) {
+ toggleRecord();
+ toggleRecord();
+ }
+ }
+
+ @Override
+ public void toggleRecord() {
+ if (getContext() instanceof ProVideoActivity) {
+ ProVideoActivity pro = (ProVideoActivity) getContext();
+
+ if (ActivityCompat.checkSelfPermission(pro, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(pro, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ProVideoActivity.REQUEST_WRITE_STORAGE + 1);
+ return;
+ }
+ }
+
+ if (!isRecording()) {
+ Uri uri = mUri;
+ if (uri == null)
+ return;
+
+ mRecordPath = "record_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".mp4";
+ File directory = new File(FileUtil.getMoviePath());
+
+ try {
+ directory.mkdirs();
+ startRecord(directory + "/" + mRecordPath, 30);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ mRecordPath = null;
+ }
+ } else {
+ stopRecord();
+ }
+ }
+
+ @Override
+ public float getSpeed() {
+ if (mMediaPlayer == null) {
+ return 1.0f;
+ }
+
+ if (mMediaPlayer instanceof IjkMediaPlayer) {
+ IjkMediaPlayer player = (IjkMediaPlayer) mMediaPlayer;
+ return player.getSpeed();
+ }
+
+ return 1.0f;
+ }
+
+ @Override
+ public void setSpeed(float speed) {
+ if (mMediaPlayer == null) {
+ return;
+ }
+
+ if (mMediaPlayer instanceof IjkMediaPlayer) {
+ IjkMediaPlayer player = (IjkMediaPlayer) mMediaPlayer;
+ player.setSpeed(speed);
+ }
+ }
+
+ @Override
+ public void takePicture() {
+ if (getContext() instanceof ProVideoActivity) {
+ ProVideoActivity pro = (ProVideoActivity) getContext();
+ pro.onTakePicture(null);
+ }
+ }
+
+ @Override
+ public void toggleMode() {
+ if (getContext() instanceof ProVideoActivity) {
+ ProVideoActivity pro = (ProVideoActivity) getContext();
+ pro.onChangePlayMode(null);
+ }
+ }
+
+ @Override
+ public boolean isCompleted() {
+ if (mMediaPlayer instanceof IjkMediaPlayer) {
+ IjkMediaPlayer player = (IjkMediaPlayer) mMediaPlayer;
+ return player.isCompleted();
+ }
+
+ return false;
+ }
+}
diff --git a/myapplication/src/main/java/com/example/myapplication/player/VideoControllerView.java b/myapplication/src/main/java/com/example/myapplication/player/VideoControllerView.java
new file mode 100644
index 0000000..436b9af
--- /dev/null
+++ b/myapplication/src/main/java/com/example/myapplication/player/VideoControllerView.java
@@ -0,0 +1,970 @@
+package com.example.myapplication.player;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.MediaController;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import com.example.myapplication.R;
+
+import java.lang.ref.WeakReference;
+import java.util.Formatter;
+import java.util.Locale;
+
+import tv.danmaku.ijk.media.widget.media.IMediaController;
+import tv.danmaku.ijk.media.widget.media.IjkVideoView;
+
+/**
+ * 播放控制器
+ *
+ * Created by jiaozebo on 2017/6/11.
+ */
+public class VideoControllerView extends FrameLayout implements IMediaController {
+ private static final String TAG = "VideoControllerView";
+
+ private MediaController.MediaPlayerControl mPlayer;
+
+ private boolean mShowing;
+ private boolean mDragging;
+
+ private static final int sDefaultTimeout = 10000;
+ private static final int FADE_OUT = 1;
+ private static final int SHOW_PROGRESS = 2;
+
+ private boolean mUseFastForward;
+ private boolean mFromXml;
+
+ StringBuilder mFormatBuilder;
+ Formatter mFormatter;
+
+ private Context mContext;
+ private View mAnchor;
+ private View mRoot;
+ private View mediaControllerLL;
+ private SeekBar mProgress; // 播放进度滚动条
+ private TextView mCurrentTime; // 当前播放时间点
+ private TextView mEndTime; // 总时长
+
+ private ImageButton mPauseButton; // 暂停or开始
+ private ImageButton mFastButton; // 快进
+ private ImageButton mRewindButton; // 快退
+ private ImageButton mFullscreenButton; // 全屏
+ private ImageButton mRecordButton; // 录像
+ private ImageButton mFastPlay; // 播放速度加快
+ private ImageButton mSlowPlay; // 播放速度减慢
+
+ private TextView mTVSpeed;
+ private TextView mTVRecordDuration;
+ private TextView fps, kbps;
+ private View mPictureBtn, mChangeModeBtn;
+
+ private long mReceivedBytes;
+ private long mReceivedPackets;
+ private long lastBitsMillis;
+ private long recordBeginTime;
+
+ private Handler mHandler = new MessageHandler(this);
+
+ Runnable mRecordTickTask = new Runnable() {
+ @Override
+ public void run() {
+ long recordSecond = (System.currentTimeMillis() - recordBeginTime) / 1000;
+
+ if (recordSecond >= 300) { // 分段
+
+ }
+
+ recordSecond %= 3600;
+ mTVRecordDuration.setText(String.format("%02d:%02d", recordSecond / 60, recordSecond % 60));
+ mTVRecordDuration.setCompoundDrawablesWithIntrinsicBounds(recordSecond % 2 == 0 ? R.drawable.red_dot : R.drawable.transparent_dot, 0, 0, 0);
+ postDelayed(this, 1000);
+ }
+ };
+
+ // 每一秒更新fps/bps
+ Runnable fpsBpsTickTask = new Runnable() {
+ long firstTimeStamp = 0l;
+
+ @Override
+ public void run() {
+ if (firstTimeStamp == 0l)
+ firstTimeStamp = System.currentTimeMillis();
+
+ if (mPlayer != null && (mPlayer instanceof IjkVideoView)) {
+ IjkVideoView ijk = (IjkVideoView) mPlayer;
+ long l = ijk.getReceivedBytes();
+ long received = l - mReceivedBytes;
+
+ long packets = ijk.getVideoCachePackets();
+ long receivedPackets = packets - mReceivedPackets;
+ mReceivedBytes = l;
+ mReceivedPackets = packets;
+
+ if (ijk.isPlaying() && lastBitsMillis != 0) {
+ long l1 = SystemClock.uptimeMillis() - lastBitsMillis;
+
+ if (l1 >= 300) {
+ long time = System.currentTimeMillis() - firstTimeStamp;
+
+ if (time < 900) {
+ fps.setText("");
+ kbps.setText("");
+ } else {
+ receivedPackets = Math.min(receivedPackets, 30);
+ fps.setText(String.format("%dfps", receivedPackets));
+ kbps.setText(String.format("%3.01fKB/s", received * 1.0f * 1000 / l1 / 1024));
+ }
+ }
+ } else {
+ fps.setText("");
+ kbps.setText("");
+ }
+
+ lastBitsMillis = SystemClock.uptimeMillis();
+ }
+ postDelayed(this, 1000);
+ }
+ };
+
+ private Runnable mSeekingPending;
+
+ // 暂停/开始的点击事件
+ private OnClickListener mPauseListener = new OnClickListener() {
+ public void onClick(View v) {
+ doPauseResume();
+ show(sDefaultTimeout);
+ }
+ };
+
+ // 全屏的点击事件
+ private OnClickListener mFullscreenListener = new OnClickListener() {
+ public void onClick(View v) {
+ doToggleFullscreen();
+ show(sDefaultTimeout);
+ }
+ };
+
+ // 录像的点击事件
+ private OnClickListener mRecordingListener = new OnClickListener() {
+ public void onClick(View v) {
+ doToggleRecord();
+ show(sDefaultTimeout);
+ }
+ };
+
+ // 滚动条的点击事件
+ private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
+ public void onStartTrackingTouch(SeekBar bar) {
+ show(3600000);
+
+ mDragging = true;
+ mHandler.removeMessages(SHOW_PROGRESS);
+ }
+
+ public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
+ if (mPlayer == null) {
+ return;
+ }
+
+ if (!fromuser) {
+ return;
+ }
+
+ if (mSeekingPending != null) {
+ removeCallbacks(mSeekingPending);
+ mSeekingPending = null;
+ }
+
+ if (mPlayer.getDuration() <= 0)
+ return;
+// long duration = mPlayer.getDuration();
+// long newPosition = (duration * progress) / 1000L;
+
+ mPlayer.seekTo((int) progress);
+
+ if (mCurrentTime != null)
+ mCurrentTime.setText(stringForTime((int) progress));
+ }
+
+ public void onStopTrackingTouch(SeekBar bar) {
+ mDragging = false;
+ setProgress();
+ updatePausePlay();
+ show(sDefaultTimeout);
+
+ mHandler.sendEmptyMessage(SHOW_PROGRESS);
+ }
+ };
+
+ // 快退的点击事件
+ private OnClickListener mRewindListener = new OnClickListener() {
+ public void onClick(View v) {
+ if (mPlayer == null) {
+ return;
+ }
+
+ int pos = mPlayer.getCurrentPosition();
+ pos -= 5000; // милисекунд
+ mPlayer.seekTo(pos);
+ setProgress();
+
+ show(sDefaultTimeout);
+ }
+ };
+
+ // 快进的点击事件
+ private OnClickListener mFastListener = new OnClickListener() {
+ public void onClick(View v) {
+ if (mPlayer == null) {
+ return;
+ }
+
+ int pos = mPlayer.getCurrentPosition();
+ pos += 15000; // милисекунд
+ mPlayer.seekTo(pos);
+ setProgress();
+
+ show(sDefaultTimeout);
+ }
+ };
+
+ // 播放速度加快
+ private OnClickListener mFastPlayListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ show(sDefaultTimeout);
+
+ if (mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ FullScreenAbleMediaPlayerControl player = (FullScreenAbleMediaPlayerControl) mPlayer;
+ float speed = player.getSpeed();
+
+ if (speed > 2.0) {
+ return;
+ }
+
+ if (speed >= 1.0f) {
+ mTVSpeed.setText(String.format("%d倍速", (int) (speed * 2)));
+ } else {
+ mTVSpeed.setText(String.format("%.02f倍速", speed * 2));
+ }
+
+ if (speed == 0.5) {
+ mTVSpeed.setVisibility(GONE);
+ } else {
+ mTVSpeed.setVisibility(VISIBLE);
+ }
+
+ player.setSpeed(speed * 2);
+ } else {
+
+ }
+ }
+ };
+
+ private OnClickListener mSlowPlayListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ FullScreenAbleMediaPlayerControl player = (FullScreenAbleMediaPlayerControl) mPlayer;
+ float speed = player.getSpeed();
+
+ if (speed < 0.5) {
+ return;
+ }
+
+ if (speed >= 2.0f) {
+ mTVSpeed.setText(String.format("%d倍速", (int) (speed * 0.5)));
+ } else {
+ mTVSpeed.setText(String.format("%.02f倍速", speed * 0.5));
+ }
+
+ if (speed == 2.0) {
+ mTVSpeed.setVisibility(GONE);
+ } else {
+ mTVSpeed.setVisibility(VISIBLE);
+ }
+
+ player.setSpeed(speed * 0.5f);
+ }
+
+ show(sDefaultTimeout);
+ }
+ };
+
+ private OnClickListener takePicListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ show(sDefaultTimeout);
+ if (mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ FullScreenAbleMediaPlayerControl player = (FullScreenAbleMediaPlayerControl) mPlayer;
+ player.takePicture();
+ }
+ }
+ };
+
+ private OnClickListener modeListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ show(sDefaultTimeout);
+ if (mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ FullScreenAbleMediaPlayerControl player = (FullScreenAbleMediaPlayerControl) mPlayer;
+ player.toggleMode();
+ }
+ }
+ };
+
+ /** ==================== constructor ==================== */
+
+ public VideoControllerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mRoot = null;
+ mContext = context;
+ mUseFastForward = true;
+ mFromXml = true;
+
+ Log.i(TAG, TAG);
+ }
+
+ public VideoControllerView(Context context, boolean useFastForward) {
+ super(context);
+ mContext = context;
+ mUseFastForward = useFastForward;
+
+ Log.i(TAG, TAG);
+ }
+
+ public VideoControllerView(Context context) {
+ this(context, true);
+
+ Log.i(TAG, TAG);
+ }
+
+ /** ==================== system Override ==================== */
+ /*
+ * 发生在视图实例化的过程中,一般在activity的oncreate方法中,并且只有在布局文件中实例化才有会这个回调
+ * */
+ @Override
+ public void onFinishInflate() {
+ super.onFinishInflate();
+
+ if (mRoot != null)
+ initControllerView(mRoot);
+ }
+
+ /*
+ * 当发生轨迹球事件时触发该方法(貌似轨迹球是过去手机的按键)
+ * */
+ @Override
+ public boolean onTrackballEvent(MotionEvent ev) {
+ show(sDefaultTimeout);
+ return false;
+ }
+
+ /*
+ * 当发生触摸屏事件时触发该方法
+ * */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ show(sDefaultTimeout);
+ return true;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mPlayer == null) {
+ return true;
+ }
+
+ int keyCode = event.getKeyCode();
+ final boolean uniqueDown = event.getRepeatCount() == 0 && event.getAction() == KeyEvent.ACTION_DOWN;
+
+ if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
+ keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ||
+ keyCode == KeyEvent.KEYCODE_SPACE) {
+
+ if (uniqueDown) {
+ doPauseResume();
+ show(sDefaultTimeout);
+
+ if (mPauseButton != null) {
+ mPauseButton.requestFocus();
+ }
+ }
+
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
+ if (uniqueDown && !mPlayer.isPlaying()) {
+ mPlayer.start();
+ updatePausePlay();
+ show(sDefaultTimeout);
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
+ || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
+ if (uniqueDown && mPlayer.isPlaying()) {
+ mPlayer.pause();
+ updatePausePlay();
+ show(sDefaultTimeout);
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
+ keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
+ keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
+ return super.dispatchKeyEvent(event);
+ } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
+ if (uniqueDown) {
+ hide();
+ }
+
+ return true;
+ }
+
+ show(sDefaultTimeout);
+ return super.dispatchKeyEvent(event);
+ }
+
+ /** ==================== IMediaController ==================== */
+
+ @Override
+ public void hide() {
+ if (mAnchor == null) {
+ return;
+ }
+
+ try {
+ if (mAnchor instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) mAnchor;
+ vg.removeView(this);
+ }
+
+ mHandler.removeMessages(SHOW_PROGRESS);
+ } catch (IllegalArgumentException ex) {
+ Log.w("MediaController", "already removed");
+ }
+
+ mShowing = false;
+ }
+
+ public boolean isShowing() {
+ return mShowing;
+ }
+
+ @Override
+ public void setAnchorView(View view) {
+ mAnchor = view;
+
+ LayoutParams frameParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+
+ removeAllViews();
+
+ View v = makeControllerView();
+ addView(v, frameParams);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (mPauseButton != null) {
+ mPauseButton.setEnabled(enabled);
+ }
+
+ if (mFastButton != null) {
+ mFastButton.setEnabled(enabled);
+ }
+
+ if (mRewindButton != null) {
+ mRewindButton.setEnabled(enabled);
+ }
+
+ if (mProgress != null) {
+ mProgress.setEnabled(enabled);
+ }
+
+ disableUnsupportedButtons();
+
+ super.setEnabled(enabled);
+ }
+
+ @Override
+ public void setMediaPlayer(MediaController.MediaPlayerControl player) {
+ mPlayer = player;
+
+ updatePausePlay();
+ updateFullScreen();
+ updateRecord();
+ updateSpeedCtrl();
+ }
+
+ @Override
+ public void show() {
+ show(sDefaultTimeout);
+ }
+
+ @Override
+ public void show(int timeout) {
+ if (!mShowing && mAnchor != null) {
+ setProgress();
+
+ if (mPauseButton != null) {
+ mPauseButton.requestFocus();
+ }
+
+ disableUnsupportedButtons();
+
+ LayoutParams tlp = new LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ Gravity.BOTTOM);
+
+ if (mAnchor instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) mAnchor;
+ vg.addView(this, tlp);
+ }
+
+ mShowing = true;
+ }
+
+ updatePausePlay();
+ updateFullScreen();
+ updateRecord();
+ updateSpeedCtrl();
+
+ mHandler.sendEmptyMessage(SHOW_PROGRESS);
+
+ Message msg = mHandler.obtainMessage(FADE_OUT);
+
+ if (timeout != 0) {
+ mHandler.removeMessages(FADE_OUT);
+ mHandler.sendMessageDelayed(msg, timeout);
+ }
+
+ if (mPlayer != null && mPlayer.isPlaying()) {
+ removeCallbacks(fpsBpsTickTask);
+ post(fpsBpsTickTask);
+ }
+ }
+
+ @Override
+ public void showOnce(View view) {
+
+ }
+
+ /** ==================== UI操作 ==================== */
+
+ /**
+ * 生成播放控制的布局
+ */
+ protected View makeControllerView() {
+ LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mRoot = inflate.inflate(R.layout.media_controller, null);
+
+ initControllerView(mRoot);
+
+ return mRoot;
+ }
+
+ private void initControllerView(View v) {
+ mediaControllerLL = (LinearLayout) v.findViewById(R.id.media_controller_ll);
+
+ mPauseButton = (ImageButton) v.findViewById(R.id.pause);
+ if (mPauseButton != null) {
+ mPauseButton.requestFocus();
+ mPauseButton.setOnClickListener(mPauseListener);
+ }
+
+ mFullscreenButton = (ImageButton) v.findViewById(R.id.fullscreen);
+ if (mFullscreenButton != null) {
+ mFullscreenButton.requestFocus();
+ mFullscreenButton.setOnClickListener(mFullscreenListener);
+ }
+
+ mRecordButton = (ImageButton) v.findViewById(R.id.action_record);
+ if (mRecordButton != null) {
+ mRecordButton.requestFocus();
+ mRecordButton.setOnClickListener(mRecordingListener);
+ }
+
+ mFastButton = (ImageButton) v.findViewById(R.id.fast_forward);
+ if (mFastButton != null) {
+ mFastButton.setOnClickListener(mFastListener);
+ if (!mFromXml) {
+ mFastButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ mRewindButton = (ImageButton) v.findViewById(R.id.rewind);
+ if (mRewindButton != null) {
+ mRewindButton.setOnClickListener(mRewindListener);
+ if (!mFromXml) {
+ mRewindButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ mProgress = (SeekBar) v.findViewById(R.id.media_controller_progress);
+ if (mProgress != null) {
+ if (mProgress instanceof SeekBar) {
+ SeekBar seeker = (SeekBar) mProgress;
+ seeker.setOnSeekBarChangeListener(mSeekListener);
+ }
+
+ mProgress.setMax(1000);
+ }
+
+ fps = (TextView) v.findViewById(R.id.tv_fps);
+ kbps = (TextView) v.findViewById(R.id.tv_kbps);
+ mEndTime = (TextView) v.findViewById(R.id.total_time);
+ mCurrentTime = (TextView) v.findViewById(R.id.time_current);
+ mTVSpeed = (TextView) v.findViewById(R.id.tv_speed);
+
+ mFormatBuilder = new StringBuilder();
+ mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
+
+ mFastPlay = (ImageButton) v.findViewById(R.id.fast);
+ mFastPlay.setOnClickListener(mFastPlayListener);
+
+ mSlowPlay = (ImageButton) v.findViewById(R.id.slow);
+ mSlowPlay.setOnClickListener(mSlowPlayListener);
+
+ mPictureBtn = v.findViewById(R.id.action_take_picture);
+ mPictureBtn.setOnClickListener(takePicListener);
+
+ mChangeModeBtn = v.findViewById(R.id.action_change_mode);
+ mChangeModeBtn.setOnClickListener(modeListener);
+
+ mTVRecordDuration = (TextView) v.findViewById(R.id.tv_record_time);
+ mTVRecordDuration.setOnClickListener(mRecordingListener);
+
+ if (this.mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ FullScreenAbleMediaPlayerControl mPlayer = (FullScreenAbleMediaPlayerControl) this.mPlayer;
+ } else {
+ mFastPlay.setVisibility(GONE);
+ mSlowPlay.setVisibility(GONE);
+ }
+
+ if (mPlayer.isPlaying()) {
+ post(fpsBpsTickTask);
+ }
+
+ if (!mPlayer.canSeekBackward() || !mPlayer.canSeekForward()) {
+ v.findViewById(R.id.seek_bar_container).setVisibility(GONE);
+ } else {
+ v.findViewById(R.id.seek_bar_container).setVisibility(VISIBLE);
+ }
+ }
+
+ /**
+ * Отключить паузу или seek button, если поток не может быть приостановлена
+ * Это требует интерфейс управления MediaPlayerControlExt
+ */
+ private void disableUnsupportedButtons() {
+ if (mPlayer == null) {
+ return;
+ }
+
+ try {
+ if (mPauseButton != null && !mPlayer.canPause()) {
+ mPauseButton.setEnabled(false);
+ }
+
+ if (mRewindButton != null && !mPlayer.canSeekBackward()) {
+ mRewindButton.setEnabled(false);
+ }
+
+ if (mFastButton != null && !mPlayer.canSeekForward()) {
+ mFastButton.setEnabled(false);
+ }
+ } catch (IncompatibleClassChangeError ex) {
+ //выводите в лог что хотите из ex
+ }
+ }
+
+ private void updateSpeedCtrl() {
+ if (mRoot == null || mRecordButton == null || this.mPlayer == null) {
+ return;
+ }
+
+ if (this.mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ FullScreenAbleMediaPlayerControl mPlayer = (FullScreenAbleMediaPlayerControl) this.mPlayer;
+
+ if (mPlayer.speedCtrlEnable()) {
+ mFastPlay.setVisibility(VISIBLE);
+ mSlowPlay.setVisibility(VISIBLE);
+ mTVSpeed.setVisibility(VISIBLE);
+
+ kbps.setVisibility(GONE);
+ fps.setVisibility(GONE);
+ } else {
+ mFastPlay.setVisibility(GONE);
+ mSlowPlay.setVisibility(GONE);
+ mTVSpeed.setVisibility(GONE);
+
+ kbps.setVisibility(VISIBLE);
+ fps.setVisibility(VISIBLE);
+ }
+ }
+ }
+
+ /*
+ * 暂停/开始播放
+ * */
+ private void doPauseResume() {
+ if (mPlayer == null) {
+ return;
+ }
+
+ removeCallbacks(fpsBpsTickTask);
+
+ if (mPlayer.isPlaying()) {
+ mPlayer.pause();
+ } else {
+ boolean isCompleted = false;
+
+ if (this.mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ FullScreenAbleMediaPlayerControl mPlayer = (FullScreenAbleMediaPlayerControl) this.mPlayer;
+ isCompleted = mPlayer.isCompleted();
+ }
+
+ mPlayer.start();
+
+ if (isCompleted) {
+ int duration = mPlayer.getDuration();
+ final int progress = mProgress.getProgress();
+ Log.d(TAG,String.valueOf(duration));
+
+ mSeekingPending = new Runnable() {
+ @Override
+ public void run() {
+ if (mPlayer != null) {
+ mPlayer.seekTo(progress);
+ }
+ }
+ };
+
+ postDelayed(mSeekingPending,500);
+ }
+
+ post(fpsBpsTickTask);
+ mReceivedBytes = 0;
+ mReceivedPackets = 0;
+ }
+
+ updatePausePlay();
+ }
+
+ public void updatePausePlay() {
+ if (mRoot == null || mPauseButton == null || mPlayer == null) {
+ return;
+ }
+
+ if (mPlayer.isPlaying()) {
+ if (this.mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ mPauseButton.setImageResource(R.drawable.new_stop_white);
+ } else {
+ mPauseButton.setImageResource(R.drawable.new_stop);
+ }
+ } else {
+ if (this.mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ mPauseButton.setImageResource(R.drawable.new_play_white);
+ } else {
+ mPauseButton.setImageResource(R.drawable.new_play);
+ }
+ }
+ }
+
+ private int setProgress() {
+ if (mPlayer == null || mDragging) {
+ return 0;
+ }
+
+ int position = (int) (mPlayer.getCurrentPosition());
+
+ // 非文件流的duration为0.
+ int duration = mPlayer.getDuration();
+
+ if (mProgress != null) {
+ if (duration > 0) {
+ int max = mProgress.getMax();
+
+ if (max != duration) {
+ mProgress.setMax(duration);
+ mProgress.setProgress(position);
+ } else {
+ if (position > mProgress.getProgress()){
+ mProgress.setProgress(position);
+ }
+ }
+ } else {
+ mProgress.setMax(0);
+ mProgress.setProgress(0);
+ }
+
+ int percent = mPlayer.getBufferPercentage();
+ mProgress.setSecondaryProgress(percent * 10);
+ }
+
+ if (mEndTime != null)
+ mEndTime.setText(stringForTime(duration));
+
+ if (mCurrentTime != null)
+ mCurrentTime.setText(stringForTime(position));
+
+ return position;
+ }
+
+ public void updateFullScreen() {
+ if (mRoot == null || mFullscreenButton == null || this.mPlayer == null) {
+ return;
+ }
+
+ if (this.mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ FullScreenAbleMediaPlayerControl mPlayer = (FullScreenAbleMediaPlayerControl) this.mPlayer;
+ if (mPlayer.isFullScreen()) {
+ mFullscreenButton.setImageResource(R.drawable.new_full);
+ } else {
+ mFullscreenButton.setImageResource(R.drawable.new_full_white);
+ }
+ }
+ }
+
+ private void updateRecord() {
+ if (mRoot == null || mRecordButton == null || this.mPlayer == null) {
+ return;
+ }
+
+ if (this.mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ FullScreenAbleMediaPlayerControl mPlayer = (FullScreenAbleMediaPlayerControl) this.mPlayer;
+
+ if (mPlayer.isRecording()) {
+ mRecordButton.setImageResource(R.drawable.new_videotape_click);
+
+ removeCallbacks(mRecordTickTask);
+ post(mRecordTickTask);
+ } else {
+ mRecordButton.setImageResource(R.drawable.new_videotape_btn);
+ }
+
+ if (mPlayer.recordEnable()) {
+ mRecordButton.setVisibility(VISIBLE);
+ } else {
+ mRecordButton.setVisibility(GONE);
+ }
+ }
+ }
+
+ private void doToggleFullscreen() {
+ if (mPlayer == null) {
+ return;
+ }
+
+ if (this.mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ FullScreenAbleMediaPlayerControl mPlayer = (FullScreenAbleMediaPlayerControl) this.mPlayer;
+ mPlayer.toggleFullScreen();
+ }
+
+ updateFullScreen();
+ }
+
+ private void doToggleRecord() {
+ if (mPlayer == null) {
+ return;
+ }
+
+ if (this.mPlayer instanceof FullScreenAbleMediaPlayerControl) {
+ FullScreenAbleMediaPlayerControl mPlayer = (FullScreenAbleMediaPlayerControl) this.mPlayer;
+ mPlayer.toggleRecord();
+
+ if (mPlayer.isRecording()) {
+ findViewById(R.id.tv_record_time).setVisibility(VISIBLE);
+ recordBeginTime = System.currentTimeMillis();
+ post(mRecordTickTask);
+ } else {
+ findViewById(R.id.tv_record_time).setVisibility(GONE);
+ removeCallbacks(mRecordTickTask);
+ }
+ }
+ }
+
+ private String stringForTime(int timeMs) {
+ int totalSeconds = timeMs / 1000;
+ int seconds = totalSeconds % 60;
+ int minutes = (totalSeconds / 60) % 60;
+ int hours = totalSeconds / 3600;
+
+ mFormatBuilder.setLength(0);
+
+ if (hours > 0) {
+ return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
+ } else {
+ return mFormatter.format("%02d:%02d", minutes, seconds).toString();
+ }
+ }
+
+ public interface FullScreenAbleMediaPlayerControl extends MediaController.MediaPlayerControl {
+ boolean isFullScreen();
+
+ void toggleFullScreen();
+
+ boolean recordEnable();
+
+ boolean speedCtrlEnable();
+
+ boolean isRecording();
+
+ void toggleRecord();
+
+ float getSpeed();
+
+ void setSpeed(float speed);
+
+ void takePicture();
+
+ void toggleMode();
+
+ boolean isCompleted();
+ }
+
+ private static class MessageHandler extends Handler {
+ private final WeakReference mView;
+
+ MessageHandler(VideoControllerView view) {
+ mView = new WeakReference(view);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ VideoControllerView view = mView.get();
+
+ if (view == null || view.mPlayer == null) {
+ return;
+ }
+
+ switch (msg.what) {
+ case FADE_OUT:
+ view.hide();
+ break;
+ case SHOW_PROGRESS:
+ if (!view.mDragging && view.mShowing && view.mPlayer.isPlaying()) {
+ msg = obtainMessage(SHOW_PROGRESS);
+ sendMessageDelayed(msg, 1000 - (view.setProgress() % 1000));
+ }
+ break;
+ }
+ }
+ }
+}
+
diff --git a/myapplication/src/main/java/com/example/myapplication/player/utils/FileUtil.java b/myapplication/src/main/java/com/example/myapplication/player/utils/FileUtil.java
new file mode 100644
index 0000000..b0736c8
--- /dev/null
+++ b/myapplication/src/main/java/com/example/myapplication/player/utils/FileUtil.java
@@ -0,0 +1,39 @@
+package com.example.myapplication.player.utils;
+
+import android.os.Environment;
+
+import java.io.File;
+
+public class FileUtil {
+
+ private static String path = Environment.getExternalStorageDirectory() +"/EasyPlayerRro";
+
+ public static String getPicturePath() {
+ return path + "/picture";
+ }
+
+ public static File getSnapshotFile(String url) {
+ File file = new File(getPicturePath() + urlDir(url));
+ file.mkdirs();
+
+ file = new File(file, "snapshot.jpg");
+
+ return file;
+ }
+
+ public static String getMoviePath() {
+ return path + "/movie";
+ }
+
+ private static String urlDir(String url) {
+ url = url.replace("://", "");
+ url = url.replace("/", "");
+ url = url.replace(".", "");
+
+ if (url.length() > 64) {
+ url.substring(0, 63);
+ }
+
+ return url;
+ }
+}
diff --git a/myapplication/src/main/java/com/example/myapplication/player/utils/SPUtil.java b/myapplication/src/main/java/com/example/myapplication/player/utils/SPUtil.java
new file mode 100644
index 0000000..31f4489
--- /dev/null
+++ b/myapplication/src/main/java/com/example/myapplication/player/utils/SPUtil.java
@@ -0,0 +1,44 @@
+package com.example.myapplication.player.utils;
+
+import android.content.Context;
+import android.preference.PreferenceManager;
+
+public class SPUtil {
+
+ /* ============================ 使用MediaCodec解码 ============================ */
+ private static final String KEY_SW_CODEC = "pref.using_media_codec";
+
+ public static boolean getMediaCodec(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(KEY_SW_CODEC, false);
+ }
+
+ public static void setMediaCodec(Context context, boolean isChecked) {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putBoolean(KEY_SW_CODEC, isChecked)
+ .apply();
+ }
+
+ /* ============================ KEY_UDP_MODE ============================ */
+ private static final String KEY_UDP_MODE = "USE_UDP_MODE";
+
+ public static boolean getUDPMode(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(KEY_UDP_MODE, false);
+ }
+
+ public static void setUDPMode(Context context, boolean isChecked) {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putBoolean(KEY_UDP_MODE, isChecked)
+ .apply();
+ }
+
+ public static void setDefaultParams(Context context) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit()
+ .putInt("timeout", 5)
+ .putLong("analyzeduration", 21000000L)
+ .apply();
+ }
+}
diff --git a/myapplication/src/main/jniLibs/arm64-v8a/libproffmpeg.so b/myapplication/src/main/jniLibs/arm64-v8a/libproffmpeg.so
new file mode 100644
index 0000000..48e7fcd
Binary files /dev/null and b/myapplication/src/main/jniLibs/arm64-v8a/libproffmpeg.so differ
diff --git a/myapplication/src/main/jniLibs/arm64-v8a/libproplayer.so b/myapplication/src/main/jniLibs/arm64-v8a/libproplayer.so
new file mode 100644
index 0000000..bca8d82
Binary files /dev/null and b/myapplication/src/main/jniLibs/arm64-v8a/libproplayer.so differ
diff --git a/myapplication/src/main/jniLibs/arm64-v8a/libprosdl.so b/myapplication/src/main/jniLibs/arm64-v8a/libprosdl.so
new file mode 100644
index 0000000..82f09ef
Binary files /dev/null and b/myapplication/src/main/jniLibs/arm64-v8a/libprosdl.so differ
diff --git a/myapplication/src/main/jniLibs/armeabi-v7a/libproffmpeg.so b/myapplication/src/main/jniLibs/armeabi-v7a/libproffmpeg.so
new file mode 100644
index 0000000..1e6bbd3
Binary files /dev/null and b/myapplication/src/main/jniLibs/armeabi-v7a/libproffmpeg.so differ
diff --git a/myapplication/src/main/jniLibs/armeabi-v7a/libproplayer.so b/myapplication/src/main/jniLibs/armeabi-v7a/libproplayer.so
new file mode 100644
index 0000000..1d38ba3
Binary files /dev/null and b/myapplication/src/main/jniLibs/armeabi-v7a/libproplayer.so differ
diff --git a/myapplication/src/main/jniLibs/armeabi-v7a/libprosdl.so b/myapplication/src/main/jniLibs/armeabi-v7a/libprosdl.so
new file mode 100644
index 0000000..46a31e8
Binary files /dev/null and b/myapplication/src/main/jniLibs/armeabi-v7a/libprosdl.so differ
diff --git a/myapplication/src/main/jniLibs/x86/libproffmpeg.so b/myapplication/src/main/jniLibs/x86/libproffmpeg.so
new file mode 100644
index 0000000..a412e27
Binary files /dev/null and b/myapplication/src/main/jniLibs/x86/libproffmpeg.so differ
diff --git a/myapplication/src/main/jniLibs/x86/libproplayer.so b/myapplication/src/main/jniLibs/x86/libproplayer.so
new file mode 100644
index 0000000..d600055
Binary files /dev/null and b/myapplication/src/main/jniLibs/x86/libproplayer.so differ
diff --git a/myapplication/src/main/jniLibs/x86/libprosdl.so b/myapplication/src/main/jniLibs/x86/libprosdl.so
new file mode 100644
index 0000000..3746423
Binary files /dev/null and b/myapplication/src/main/jniLibs/x86/libprosdl.so differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_fast_click.png b/myapplication/src/main/res/drawable-xhdpi/new_fast_click.png
new file mode 100644
index 0000000..31d6e01
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_fast_click.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_fast_white.png b/myapplication/src/main/res/drawable-xhdpi/new_fast_white.png
new file mode 100644
index 0000000..0e45d24
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_fast_white.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_forward_click.png b/myapplication/src/main/res/drawable-xhdpi/new_forward_click.png
new file mode 100644
index 0000000..bdd3b47
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_forward_click.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_forward_white.png b/myapplication/src/main/res/drawable-xhdpi/new_forward_white.png
new file mode 100644
index 0000000..165c6d2
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_forward_white.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_full.png b/myapplication/src/main/res/drawable-xhdpi/new_full.png
new file mode 100644
index 0000000..31d580c
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_full.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_full_white.png b/myapplication/src/main/res/drawable-xhdpi/new_full_white.png
new file mode 100644
index 0000000..57bce24
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_full_white.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_moveback_click.png b/myapplication/src/main/res/drawable-xhdpi/new_moveback_click.png
new file mode 100644
index 0000000..909bdf1
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_moveback_click.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_moveback_white.png b/myapplication/src/main/res/drawable-xhdpi/new_moveback_white.png
new file mode 100644
index 0000000..61c481b
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_moveback_white.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_play.png b/myapplication/src/main/res/drawable-xhdpi/new_play.png
new file mode 100644
index 0000000..39e9270
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_play.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_play_white.png b/myapplication/src/main/res/drawable-xhdpi/new_play_white.png
new file mode 100644
index 0000000..a688a73
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_play_white.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_slow_click.png b/myapplication/src/main/res/drawable-xhdpi/new_slow_click.png
new file mode 100644
index 0000000..a42ea3f
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_slow_click.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_slow_white.png b/myapplication/src/main/res/drawable-xhdpi/new_slow_white.png
new file mode 100644
index 0000000..18c8de4
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_slow_white.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_snapshot_click.png b/myapplication/src/main/res/drawable-xhdpi/new_snapshot_click.png
new file mode 100644
index 0000000..4534774
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_snapshot_click.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_snapshot_white.png b/myapplication/src/main/res/drawable-xhdpi/new_snapshot_white.png
new file mode 100644
index 0000000..6efc5ca
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_snapshot_white.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_stop.png b/myapplication/src/main/res/drawable-xhdpi/new_stop.png
new file mode 100644
index 0000000..7b546c3
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_stop.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_stop_white.png b/myapplication/src/main/res/drawable-xhdpi/new_stop_white.png
new file mode 100644
index 0000000..3c25607
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_stop_white.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_stretch_click.png b/myapplication/src/main/res/drawable-xhdpi/new_stretch_click.png
new file mode 100644
index 0000000..adc06e5
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_stretch_click.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_stretch_white.png b/myapplication/src/main/res/drawable-xhdpi/new_stretch_white.png
new file mode 100644
index 0000000..bfa52a1
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_stretch_white.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_thumb.png b/myapplication/src/main/res/drawable-xhdpi/new_thumb.png
new file mode 100644
index 0000000..b8b252b
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_thumb.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_videotape_click.png b/myapplication/src/main/res/drawable-xhdpi/new_videotape_click.png
new file mode 100644
index 0000000..ce8875c
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_videotape_click.png differ
diff --git a/myapplication/src/main/res/drawable-xhdpi/new_videotape_white.png b/myapplication/src/main/res/drawable-xhdpi/new_videotape_white.png
new file mode 100644
index 0000000..144460f
Binary files /dev/null and b/myapplication/src/main/res/drawable-xhdpi/new_videotape_white.png differ
diff --git a/myapplication/src/main/res/drawable/anim.xml b/myapplication/src/main/res/drawable/anim.xml
new file mode 100644
index 0000000..436c48c
--- /dev/null
+++ b/myapplication/src/main/res/drawable/anim.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/myapplication/src/main/res/drawable/loading.gif b/myapplication/src/main/res/drawable/loading.gif
new file mode 100644
index 0000000..06e31fd
Binary files /dev/null and b/myapplication/src/main/res/drawable/loading.gif differ
diff --git a/myapplication/src/main/res/drawable/new_fast_btn.xml b/myapplication/src/main/res/drawable/new_fast_btn.xml
new file mode 100644
index 0000000..2930484
--- /dev/null
+++ b/myapplication/src/main/res/drawable/new_fast_btn.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/myapplication/src/main/res/drawable/new_forward_btn.xml b/myapplication/src/main/res/drawable/new_forward_btn.xml
new file mode 100644
index 0000000..5936eac
--- /dev/null
+++ b/myapplication/src/main/res/drawable/new_forward_btn.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/myapplication/src/main/res/drawable/new_lost.png b/myapplication/src/main/res/drawable/new_lost.png
new file mode 100644
index 0000000..1a3984a
Binary files /dev/null and b/myapplication/src/main/res/drawable/new_lost.png differ
diff --git a/myapplication/src/main/res/drawable/new_moveback_btn.xml b/myapplication/src/main/res/drawable/new_moveback_btn.xml
new file mode 100644
index 0000000..a0e4a09
--- /dev/null
+++ b/myapplication/src/main/res/drawable/new_moveback_btn.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/myapplication/src/main/res/drawable/new_slow_btn.xml b/myapplication/src/main/res/drawable/new_slow_btn.xml
new file mode 100644
index 0000000..fec94ab
--- /dev/null
+++ b/myapplication/src/main/res/drawable/new_slow_btn.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/myapplication/src/main/res/drawable/new_snapshot_btn.xml b/myapplication/src/main/res/drawable/new_snapshot_btn.xml
new file mode 100644
index 0000000..310785f
--- /dev/null
+++ b/myapplication/src/main/res/drawable/new_snapshot_btn.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/myapplication/src/main/res/drawable/new_stretch_btn.xml b/myapplication/src/main/res/drawable/new_stretch_btn.xml
new file mode 100644
index 0000000..816dda3
--- /dev/null
+++ b/myapplication/src/main/res/drawable/new_stretch_btn.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/myapplication/src/main/res/drawable/new_videotape_btn.xml b/myapplication/src/main/res/drawable/new_videotape_btn.xml
new file mode 100644
index 0000000..67ca41b
--- /dev/null
+++ b/myapplication/src/main/res/drawable/new_videotape_btn.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/myapplication/src/main/res/drawable/red_dot.xml b/myapplication/src/main/res/drawable/red_dot.xml
new file mode 100644
index 0000000..97bad9f
--- /dev/null
+++ b/myapplication/src/main/res/drawable/red_dot.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/myapplication/src/main/res/drawable/seekbar_progress_drawable.xml b/myapplication/src/main/res/drawable/seekbar_progress_drawable.xml
new file mode 100644
index 0000000..6804e93
--- /dev/null
+++ b/myapplication/src/main/res/drawable/seekbar_progress_drawable.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/myapplication/src/main/res/drawable/transparent_dot.xml b/myapplication/src/main/res/drawable/transparent_dot.xml
new file mode 100644
index 0000000..9db24c6
--- /dev/null
+++ b/myapplication/src/main/res/drawable/transparent_dot.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/myapplication/src/main/res/layout/activity_main_pro.xml b/myapplication/src/main/res/layout/activity_main_pro.xml
new file mode 100644
index 0000000..cfa1d2d
--- /dev/null
+++ b/myapplication/src/main/res/layout/activity_main_pro.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/myapplication/src/main/res/layout/media_controller.xml b/myapplication/src/main/res/layout/media_controller.xml
new file mode 100644
index 0000000..953a204
--- /dev/null
+++ b/myapplication/src/main/res/layout/media_controller.xml
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/myapplication/src/main/res/values/attrs.xml b/myapplication/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..7ce840e
--- /dev/null
+++ b/myapplication/src/main/res/values/attrs.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/myapplication/src/main/res/values/colors.xml b/myapplication/src/main/res/values/colors.xml
index 3ab3e9c..e779ff3 100644
--- a/myapplication/src/main/res/values/colors.xml
+++ b/myapplication/src/main/res/values/colors.xml
@@ -3,4 +3,8 @@
#3F51B5
#303F9F
#FF4081
+
+ #ffffff
+ #1ED5CA
+ #66000000
diff --git a/myapplication/src/main/res/values/styles.xml b/myapplication/src/main/res/values/styles.xml
index 5885930..c68e081 100644
--- a/myapplication/src/main/res/values/styles.xml
+++ b/myapplication/src/main/res/values/styles.xml
@@ -1,11 +1,38 @@
-
+
+
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
index 06ea3e2..7e85a5e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,3 @@
include ':myapplication' //, ':libuvccamera-release'
include ':library'
+include ':ijkplayer-java'