diff --git a/ijkplayer-java/.gitignore b/ijkplayer-java/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/ijkplayer-java/.gitignore @@ -0,0 +1 @@ +/build diff --git a/ijkplayer-java/build.gradle b/ijkplayer-java/build.gradle new file mode 100644 index 0000000..7bd1f43 --- /dev/null +++ b/ijkplayer-java/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 26 + lintOptions { + abortOnError false + } + defaultConfig { + minSdkVersion 16 + targetSdkVersion 26 + consumerProguardFiles 'proguard-rules.pro' + versionCode 38 + versionName "1.1.17.1124" + } + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + buildToolsVersion '28.0.3' +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') +} + diff --git a/ijkplayer-java/proguard-rules.pro b/ijkplayer-java/proguard-rules.pro new file mode 100644 index 0000000..2d96c93 --- /dev/null +++ b/ijkplayer-java/proguard-rules.pro @@ -0,0 +1,31 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /opt/android/ADK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} +-keepparameternames +-renamesourcefileattribute SourceFile +-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod + +-keep class tv.danmaku.ijk.media.widget.media.IjkVideoView { + public protected *; +} +-keep class tv.danmaku.ijk.media.widget.media.IMediaController { + public protected *; +} + +-keep class tv.danmaku.ijk.media.player.** { + *; +} diff --git a/ijkplayer-java/src/main/.classpath b/ijkplayer-java/src/main/.classpath new file mode 100644 index 0000000..b3caa8c --- /dev/null +++ b/ijkplayer-java/src/main/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ijkplayer-java/src/main/.project b/ijkplayer-java/src/main/.project new file mode 100644 index 0000000..31663eb --- /dev/null +++ b/ijkplayer-java/src/main/.project @@ -0,0 +1,33 @@ + + + ijkplayer-java + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/ijkplayer-java/src/main/.settings/org.eclipse.jdt.core.prefs b/ijkplayer-java/src/main/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..b080d2d --- /dev/null +++ b/ijkplayer-java/src/main/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/ijkplayer-java/src/main/AndroidManifest.xml b/ijkplayer-java/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e45a047 --- /dev/null +++ b/ijkplayer-java/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/Settings.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/Settings.java new file mode 100644 index 0000000..cad08f2 --- /dev/null +++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/Settings.java @@ -0,0 +1,113 @@ +/* + * 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; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import tv.danmaku.ijk.media.player.R; + +public class Settings { + private Context mAppContext; + private SharedPreferences mSharedPreferences; + + public static final int PV_PLAYER__Auto = 0; + public static final int PV_PLAYER__AndroidMediaPlayer = 1; + public static final int PV_PLAYER__IjkMediaPlayer = 2; + public static final int PV_PLAYER__IjkExoMediaPlayer = 3; + + public Settings(Context context) { + mAppContext = context.getApplicationContext(); + mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mAppContext); + } + + public boolean getEnableBackgroundPlay() { + String key = mAppContext.getString(R.string.pref_key_enable_background_play); + return mSharedPreferences.getBoolean(key, false); + } + + public int getPlayer() { + String key = mAppContext.getString(R.string.pref_key_player); + String value = mSharedPreferences.getString(key, ""); + try { + return Integer.valueOf(value).intValue(); + } catch (NumberFormatException e) { + return 0; + } + } + + public boolean getUsingMediaCodec() { + String key = mAppContext.getString(R.string.pref_key_using_media_codec); + return mSharedPreferences.getBoolean(key, true); + } + + public boolean getUsingMediaCodecAutoRotate() { + String key = mAppContext.getString(R.string.pref_key_using_media_codec_auto_rotate); + return mSharedPreferences.getBoolean(key, false); + } + + public boolean getMediaCodecHandleResolutionChange() { + String key = mAppContext.getString(R.string.pref_key_media_codec_handle_resolution_change); + return mSharedPreferences.getBoolean(key, false); + } + + public boolean getUsingOpenSLES() { + String key = mAppContext.getString(R.string.pref_key_using_opensl_es); + return mSharedPreferences.getBoolean(key, false); + } + + public String getPixelFormat() { + String key = mAppContext.getString(R.string.pref_key_pixel_format); + return mSharedPreferences.getString(key, ""); + } + + public boolean getEnableNoView() { + String key = mAppContext.getString(R.string.pref_key_enable_no_view); + return mSharedPreferences.getBoolean(key, false); + } + + public boolean getEnableSurfaceView() { + String key = mAppContext.getString(R.string.pref_key_enable_surface_view); + return mSharedPreferences.getBoolean(key, true); + } + + public boolean getEnableTextureView() { + String key = mAppContext.getString(R.string.pref_key_enable_texture_view); + return mSharedPreferences.getBoolean(key, true); + } + + public boolean getEnableDetachedSurfaceTextureView() { + String key = mAppContext.getString(R.string.pref_key_enable_detached_surface_texture); + return mSharedPreferences.getBoolean(key, false); + } + + public boolean getUsingMediaDataSource() { + String key = mAppContext.getString(R.string.pref_key_using_mediadatasource); + return mSharedPreferences.getBoolean(key, false); + } + + public String getLastDirectory() { + String key = mAppContext.getString(R.string.pref_key_last_directory); + return mSharedPreferences.getString(key, "/"); + } + + public void setLastDirectory(String path) { + String key = mAppContext.getString(R.string.pref_key_last_directory); + mSharedPreferences.edit().putString(key, path).apply(); + } +} diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/AbstractMediaPlayer.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/AbstractMediaPlayer.java new file mode 100644 index 0000000..742602a --- /dev/null +++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/AbstractMediaPlayer.java @@ -0,0 +1,126 @@ +/* + * 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; + +import tv.danmaku.ijk.media.player.misc.IMediaDataSource; + +@SuppressWarnings("WeakerAccess") +public abstract class AbstractMediaPlayer implements IMediaPlayer { + private OnPreparedListener mOnPreparedListener; + private OnCompletionListener mOnCompletionListener; + private OnBufferingUpdateListener mOnBufferingUpdateListener; + private OnSeekCompleteListener mOnSeekCompleteListener; + private OnVideoSizeChangedListener mOnVideoSizeChangedListener; + private OnErrorListener mOnErrorListener; + private OnInfoListener mOnInfoListener; + private OnTimedTextListener mOnTimedTextListener; + protected boolean mCompleted; + + public final void setOnPreparedListener(OnPreparedListener listener) { + mOnPreparedListener = listener; + } + + public final void setOnCompletionListener(OnCompletionListener listener) { + mOnCompletionListener = listener; + } + + public final void setOnBufferingUpdateListener( + OnBufferingUpdateListener listener) { + mOnBufferingUpdateListener = listener; + } + + public final void setOnSeekCompleteListener(OnSeekCompleteListener listener) { + mOnSeekCompleteListener = listener; + } + + public final void setOnVideoSizeChangedListener( + OnVideoSizeChangedListener listener) { + mOnVideoSizeChangedListener = listener; + } + + public final void setOnErrorListener(OnErrorListener listener) { + mOnErrorListener = listener; + } + + public final void setOnInfoListener(OnInfoListener listener) { + mOnInfoListener = listener; + } + + public final void setOnTimedTextListener(OnTimedTextListener listener) { + mOnTimedTextListener = listener; + } + + public void resetListeners() { + mOnPreparedListener = null; + mOnBufferingUpdateListener = null; + mOnCompletionListener = null; + mOnSeekCompleteListener = null; + mOnVideoSizeChangedListener = null; + mOnErrorListener = null; + mOnInfoListener = null; + mOnTimedTextListener = null; + } + + protected final void notifyOnPrepared() { + if (mOnPreparedListener != null) + mOnPreparedListener.onPrepared(this); + } + + protected final void notifyOnCompletion() { + mCompleted = true; + if (mOnCompletionListener != null) + mOnCompletionListener.onCompletion(this); + } + + protected final void notifyOnBufferingUpdate(int percent) { + if (mOnBufferingUpdateListener != null) + mOnBufferingUpdateListener.onBufferingUpdate(this, percent); + } + + protected final void notifyOnSeekComplete() { + if (mOnSeekCompleteListener != null) + mOnSeekCompleteListener.onSeekComplete(this); + } + + protected final void notifyOnVideoSizeChanged(int width, int height, + int sarNum, int sarDen) { + if (mOnVideoSizeChangedListener != null) + mOnVideoSizeChangedListener.onVideoSizeChanged(this, width, height, + sarNum, sarDen); + } + + protected final boolean notifyOnError(int what, int extra) { + return mOnErrorListener != null && mOnErrorListener.onError(this, what, extra); + } + + protected final boolean notifyOnInfo(int what, int extra) { + return mOnInfoListener != null && mOnInfoListener.onInfo(this, what, extra); + } + + protected final void notifyOnTimedText(IjkTimedText text) { + if (mOnTimedTextListener != null) + mOnTimedTextListener.onTimedText(this, text); + } + + public void setDataSource(IMediaDataSource mediaDataSource) { + throw new UnsupportedOperationException(); + } + + public boolean isCompleted(){ + return mCompleted; + } +} diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java new file mode 100644 index 0000000..8689851 --- /dev/null +++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * 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; + +import android.annotation.TargetApi; +import android.content.Context; +import android.media.AudioManager; +import android.media.MediaDataSource; +import android.media.MediaPlayer; +import android.media.TimedText; +import android.net.Uri; +import android.os.Build; +import android.text.TextUtils; +import android.view.Surface; +import android.view.SurfaceHolder; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.Map; + +import tv.danmaku.ijk.media.player.misc.AndroidTrackInfo; +import tv.danmaku.ijk.media.player.misc.IMediaDataSource; +import tv.danmaku.ijk.media.player.misc.ITrackInfo; +import tv.danmaku.ijk.media.player.pragma.DebugLog; + +public class AndroidMediaPlayer extends AbstractMediaPlayer { + private final MediaPlayer mInternalMediaPlayer; + private final AndroidMediaPlayerListenerHolder mInternalListenerAdapter; + private String mDataSource; + private MediaDataSource mMediaDataSource; + + private final Object mInitLock = new Object(); + private boolean mIsReleased; + + private static MediaInfo sMediaInfo; + + public AndroidMediaPlayer() { + synchronized (mInitLock) { + mInternalMediaPlayer = new MediaPlayer(); + } + mInternalMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mInternalListenerAdapter = new AndroidMediaPlayerListenerHolder(this); + attachInternalListeners(); + } + + public MediaPlayer getInternalMediaPlayer() { + return mInternalMediaPlayer; + } + + @Override + public void setDisplay(SurfaceHolder sh) { + synchronized (mInitLock) { + if (!mIsReleased) { + mInternalMediaPlayer.setDisplay(sh); + } + } + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void setSurface(Surface surface) { + mInternalMediaPlayer.setSurface(surface); + } + + @Override + public void setDataSource(Context context, Uri uri) + throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { + mInternalMediaPlayer.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 { + mInternalMediaPlayer.setDataSource(context, uri, headers); + } + + @Override + public void setDataSource(FileDescriptor fd) + throws IOException, IllegalArgumentException, IllegalStateException { + mInternalMediaPlayer.setDataSource(fd); + } + + @Override + public void setDataSource(String path) throws IOException, + IllegalArgumentException, SecurityException, IllegalStateException { + mDataSource = path; + + Uri uri = Uri.parse(path); + String scheme = uri.getScheme(); + if (!TextUtils.isEmpty(scheme) && scheme.equalsIgnoreCase("file")) { + mInternalMediaPlayer.setDataSource(uri.getPath()); + } else { + mInternalMediaPlayer.setDataSource(path); + } + } + + @TargetApi(Build.VERSION_CODES.M) + @Override + public void setDataSource(IMediaDataSource mediaDataSource) { + releaseMediaDataSource(); + + mMediaDataSource = new MediaDataSourceProxy(mediaDataSource); + mInternalMediaPlayer.setDataSource(mMediaDataSource); + } + + @TargetApi(Build.VERSION_CODES.M) + private static class MediaDataSourceProxy extends MediaDataSource { + private final IMediaDataSource mMediaDataSource; + + public MediaDataSourceProxy(IMediaDataSource mediaDataSource) { + mMediaDataSource = mediaDataSource; + } + + @Override + public int readAt(long position, byte[] buffer, int offset, int size) throws IOException { + return mMediaDataSource.readAt(position, buffer, offset, size); + } + + @Override + public long getSize() throws IOException { + return mMediaDataSource.getSize(); + } + + @Override + public void close() throws IOException { + mMediaDataSource.close(); + } + } + + @Override + public String getDataSource() { + return mDataSource; + } + + private void releaseMediaDataSource() { + if (mMediaDataSource != null) { + try { + mMediaDataSource.close(); + } catch (IOException e) { + e.printStackTrace(); + } + mMediaDataSource = null; + } + } + + @Override + public void prepareAsync() throws IllegalStateException { + mInternalMediaPlayer.prepareAsync(); + } + + @Override + public void start() throws IllegalStateException { + mInternalMediaPlayer.start(); + } + + @Override + public void stop() throws IllegalStateException { + mInternalMediaPlayer.stop(); + } + + @Override + public void pause() throws IllegalStateException { + mInternalMediaPlayer.pause(); + } + + @Override + public void setScreenOnWhilePlaying(boolean screenOn) { + mInternalMediaPlayer.setScreenOnWhilePlaying(screenOn); + } + + @Override + public ITrackInfo[] getTrackInfo() { + return AndroidTrackInfo.fromMediaPlayer(mInternalMediaPlayer); + } + + @Override + public int getVideoWidth() { + return mInternalMediaPlayer.getVideoWidth(); + } + + @Override + public int getVideoHeight() { + return mInternalMediaPlayer.getVideoHeight(); + } + + @Override + public int getVideoSarNum() { + return 1; + } + + @Override + public int getVideoSarDen() { + return 1; + } + + @Override + public boolean isPlaying() { + try { + return mInternalMediaPlayer.isPlaying(); + } catch (IllegalStateException e) { + DebugLog.printStackTrace(e); + return false; + } + } + + @Override + public void seekTo(long msec) throws IllegalStateException { + mInternalMediaPlayer.seekTo((int) msec); + } + + @Override + public long getCurrentPosition() { + try { + return mInternalMediaPlayer.getCurrentPosition(); + } catch (IllegalStateException e) { + DebugLog.printStackTrace(e); + return 0; + } + } + + @Override + public long getDuration() { + try { + return mInternalMediaPlayer.getDuration(); + } catch (IllegalStateException e) { + DebugLog.printStackTrace(e); + return 0; + } + } + + @Override + public void release() { + mIsReleased = true; + mInternalMediaPlayer.release(); + releaseMediaDataSource(); + resetListeners(); + attachInternalListeners(); + } + + @Override + public void reset() { + try { + mInternalMediaPlayer.reset(); + } catch (IllegalStateException e) { + DebugLog.printStackTrace(e); + } + releaseMediaDataSource(); + resetListeners(); + attachInternalListeners(); + } + + @Override + public void record(String path, int seconds) { + + } + + @Override + public void setLooping(boolean looping) { + mInternalMediaPlayer.setLooping(looping); + } + + @Override + public boolean isLooping() { + return mInternalMediaPlayer.isLooping(); + } + + @Override + public void setVolume(float leftVolume, float rightVolume) { + mInternalMediaPlayer.setVolume(leftVolume, rightVolume); + } + + @Override + public int getAudioSessionId() { + return mInternalMediaPlayer.getAudioSessionId(); + } + + @Override + public MediaInfo getMediaInfo() { + if (sMediaInfo == null) { + MediaInfo module = new MediaInfo(); + + module.mVideoDecoder = "android"; + module.mVideoDecoderImpl = "HW"; + + module.mAudioDecoder = "android"; + module.mAudioDecoderImpl = "HW"; + + sMediaInfo = module; + } + + return sMediaInfo; + } + + @Override + public void setLogEnabled(boolean enable) { + } + + @Override + public boolean isPlayable() { + return true; + } + + /*-------------------- + * misc + */ + @Override + public void setWakeMode(Context context, int mode) { + mInternalMediaPlayer.setWakeMode(context, mode); + } + + @Override + public void setAudioStreamType(int streamtype) { + mInternalMediaPlayer.setAudioStreamType(streamtype); + } + + @Override + public void setKeepInBackground(boolean keepInBackground) { + } + + /*-------------------- + * Listeners adapter + */ + private void attachInternalListeners() { + mInternalMediaPlayer.setOnPreparedListener(mInternalListenerAdapter); + mInternalMediaPlayer + .setOnBufferingUpdateListener(mInternalListenerAdapter); + mInternalMediaPlayer.setOnCompletionListener(mInternalListenerAdapter); + mInternalMediaPlayer + .setOnSeekCompleteListener(mInternalListenerAdapter); + mInternalMediaPlayer + .setOnVideoSizeChangedListener(mInternalListenerAdapter); + mInternalMediaPlayer.setOnErrorListener(mInternalListenerAdapter); + mInternalMediaPlayer.setOnInfoListener(mInternalListenerAdapter); + mInternalMediaPlayer.setOnTimedTextListener(mInternalListenerAdapter); + } + + private class AndroidMediaPlayerListenerHolder implements + MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, + MediaPlayer.OnBufferingUpdateListener, + MediaPlayer.OnSeekCompleteListener, + MediaPlayer.OnVideoSizeChangedListener, + MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener, + MediaPlayer.OnTimedTextListener { + public final WeakReference mWeakMediaPlayer; + + public AndroidMediaPlayerListenerHolder(AndroidMediaPlayer mp) { + mWeakMediaPlayer = new WeakReference(mp); + } + + @Override + public boolean onInfo(MediaPlayer mp, int what, int extra) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + return self != null && notifyOnInfo(what, extra); + + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + return self != null && notifyOnError(what, extra); + + } + + @Override + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return; + + notifyOnVideoSizeChanged(width, height, 1, 1); + } + + @Override + public void onSeekComplete(MediaPlayer mp) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return; + + notifyOnSeekComplete(); + } + + @Override + public void onBufferingUpdate(MediaPlayer mp, int percent) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return; + + notifyOnBufferingUpdate(percent); + } + + @Override + public void onCompletion(MediaPlayer mp) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return; + + notifyOnCompletion(); + } + + @Override + public void onPrepared(MediaPlayer mp) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return; + + notifyOnPrepared(); + } + + @Override + public void onTimedText(MediaPlayer mp, TimedText text) { + AndroidMediaPlayer self = mWeakMediaPlayer.get(); + if (self == null) + return; + + IjkTimedText ijkText = new IjkTimedText(text.getBounds(), text.getText()); + notifyOnTimedText(ijkText); + } + } +} diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IMediaPlayer.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IMediaPlayer.java new file mode 100644 index 0000000..cd0e096 --- /dev/null +++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IMediaPlayer.java @@ -0,0 +1,208 @@ +/* + * 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; + +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 interface IMediaPlayer { + /* + * Do not change these values without updating their counterparts in native + */ + int MEDIA_INFO_UNKNOWN = 1; + int MEDIA_INFO_STARTED_AS_NEXT = 2; + int MEDIA_INFO_VIDEO_RENDERING_START = 3; + int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; + int MEDIA_INFO_BUFFERING_START = 701; + int MEDIA_INFO_BUFFERING_END = 702; + int MEDIA_INFO_NETWORK_BANDWIDTH = 703; + int MEDIA_INFO_BAD_INTERLEAVING = 800; + int MEDIA_INFO_NOT_SEEKABLE = 801; + int MEDIA_INFO_METADATA_UPDATE = 802; + int MEDIA_INFO_TIMED_TEXT_ERROR = 900; + int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; + int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; + + int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001; + int MEDIA_INFO_AUDIO_RENDERING_START = 10002; + + int MEDIA_ERROR_UNKNOWN = 1; + int MEDIA_ERROR_SERVER_DIED = 100; + int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200; + int MEDIA_ERROR_IO = -1004; + int MEDIA_ERROR_MALFORMED = -1007; + int MEDIA_ERROR_UNSUPPORTED = -1010; + int MEDIA_ERROR_TIMED_OUT = -110; + + void setDisplay(SurfaceHolder sh); + + void setDataSource(Context context, Uri uri) + throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + void setDataSource(Context context, Uri uri, Map headers) + throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; + + void setDataSource(FileDescriptor fd) + throws IOException, IllegalArgumentException, IllegalStateException; + + void setDataSource(String path) + throws IOException, IllegalArgumentException, SecurityException, IllegalStateException; + + String getDataSource(); + + void prepareAsync() throws IllegalStateException; + + void start() throws IllegalStateException; + + void stop() throws IllegalStateException; + + void pause() throws IllegalStateException; + + void setScreenOnWhilePlaying(boolean screenOn); + + int getVideoWidth(); + + int getVideoHeight(); + + boolean isPlaying(); + + void seekTo(long msec) throws IllegalStateException; + + long getCurrentPosition(); + + long getDuration(); + + void release(); + + void reset(); + + void record(String path, int seconds); + + void setVolume(float leftVolume, float rightVolume); + + int getAudioSessionId(); + + MediaInfo getMediaInfo(); + + @SuppressWarnings("EmptyMethod") + @Deprecated + void setLogEnabled(boolean enable); + + @Deprecated + boolean isPlayable(); + + void setOnPreparedListener(OnPreparedListener listener); + + void setOnCompletionListener(OnCompletionListener listener); + + void setOnBufferingUpdateListener( + OnBufferingUpdateListener listener); + + void setOnSeekCompleteListener( + OnSeekCompleteListener listener); + + void setOnVideoSizeChangedListener( + OnVideoSizeChangedListener listener); + + void setOnErrorListener(OnErrorListener listener); + + void setOnInfoListener(OnInfoListener listener); + + void setOnTimedTextListener(OnTimedTextListener listener); + + /*-------------------- + * Listeners + */ + interface OnPreparedListener { + void onPrepared(IMediaPlayer mp); + } + + interface OnCompletionListener { + void onCompletion(IMediaPlayer mp); + } + + interface OnBufferingUpdateListener { + void onBufferingUpdate(IMediaPlayer mp, int percent); + } + + interface OnSeekCompleteListener { + void onSeekComplete(IMediaPlayer mp); + } + + interface OnVideoSizeChangedListener { + void onVideoSizeChanged(IMediaPlayer mp, int width, int height, + int sar_num, int sar_den); + } + + interface OnErrorListener { + boolean onError(IMediaPlayer mp, int what, int extra); + } + + interface OnInfoListener { + boolean onInfo(IMediaPlayer mp, int what, int extra); + } + + interface OnTimedTextListener { + void onTimedText(IMediaPlayer mp, IjkTimedText text); + } + + /*-------------------- + * Optional + */ + void setAudioStreamType(int streamtype); + + @Deprecated + void setKeepInBackground(boolean keepInBackground); + + int getVideoSarNum(); + + int getVideoSarDen(); + + @Deprecated + void setWakeMode(Context context, int mode); + + void setLooping(boolean looping); + + boolean isLooping(); + + /*-------------------- + * AndroidMediaPlayer: JELLY_BEAN + */ + ITrackInfo[] getTrackInfo(); + + /*-------------------- + * AndroidMediaPlayer: ICE_CREAM_SANDWICH: + */ + void setSurface(Surface surface); + + /*-------------------- + * AndroidMediaPlayer: M: + */ + void setDataSource(IMediaDataSource mediaDataSource); +} diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/ISurfaceTextureHolder.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/ISurfaceTextureHolder.java new file mode 100644 index 0000000..d62e9cb --- /dev/null +++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/ISurfaceTextureHolder.java @@ -0,0 +1,27 @@ +/* + * 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.graphics.SurfaceTexture; + +public interface ISurfaceTextureHolder { + void setSurfaceTexture(SurfaceTexture surfaceTexture); + + SurfaceTexture getSurfaceTexture(); + + void setSurfaceTextureHost(ISurfaceTextureHost surfaceTextureHost); +} diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/ISurfaceTextureHost.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/ISurfaceTextureHost.java new file mode 100644 index 0000000..ff10c56 --- /dev/null +++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/ISurfaceTextureHost.java @@ -0,0 +1,23 @@ +/* + * 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.graphics.SurfaceTexture; + +public interface ISurfaceTextureHost { + void releaseSurfaceTexture(SurfaceTexture surfaceTexture); +} diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkLibLoader.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkLibLoader.java new file mode 100644 index 0000000..f846fc6 --- /dev/null +++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkLibLoader.java @@ -0,0 +1,22 @@ +/* + * 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 interface IjkLibLoader { + void loadLibrary(String libName) throws UnsatisfiedLinkError, + SecurityException; +} diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaCodecInfo.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaCodecInfo.java new file mode 100644 index 0000000..4428c40 --- /dev/null +++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaCodecInfo.java @@ -0,0 +1,293 @@ +package tv.danmaku.ijk.media.player; + +import android.annotation.TargetApi; +import android.media.MediaCodecInfo; +import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaCodecInfo.CodecProfileLevel; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; + +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; + +public class IjkMediaCodecInfo { + private final static String TAG = "IjkMediaCodecInfo"; + + public static final int RANK_MAX = 1000; + public static final int RANK_TESTED = 800; + public static final int RANK_ACCEPTABLE = 700; + public static final int RANK_LAST_CHANCE = 600; + public static final int RANK_SECURE = 300; + public static final int RANK_SOFTWARE = 200; + public static final int RANK_NON_STANDARD = 100; + public static final int RANK_NO_SENSE = 0; + + public MediaCodecInfo mCodecInfo; + public int mRank = 0; + public String mMimeType; + + private static Map sKnownCodecList; + + private static synchronized Map getKnownCodecList() { + if (sKnownCodecList != null) + return sKnownCodecList; + + sKnownCodecList = new TreeMap( + String.CASE_INSENSITIVE_ORDER); + + // ----- Nvidia ----- + // Tegra3 + // Nexus 7 (2012) + // Tegra K1 + // Nexus 9 + sKnownCodecList.put("OMX.Nvidia.h264.decode", RANK_TESTED); + sKnownCodecList.put("OMX.Nvidia.h264.decode.secure", RANK_SECURE); + + // ----- Intel ----- + // Atom Z3735 + // Teclast X98 Air + sKnownCodecList.put("OMX.Intel.hw_vd.h264", RANK_TESTED + 1); + // Atom Z2560 + // Dell Venue 7 3730 + sKnownCodecList.put("OMX.Intel.VideoDecoder.AVC", RANK_TESTED); + + // ----- Qualcomm ----- + // MSM8260 + // Xiaomi MI 1S + sKnownCodecList.put("OMX.qcom.video.decoder.avc", RANK_TESTED); + sKnownCodecList.put("OMX.ittiam.video.decoder.avc", RANK_NO_SENSE); + + // ----- Samsung ----- + // Exynos 3110 + // Nexus S + sKnownCodecList.put("OMX.SEC.avc.dec", RANK_TESTED); + sKnownCodecList.put("OMX.SEC.AVC.Decoder", RANK_TESTED - 1); + // OMX.SEC.avcdec doesn't reorder output pictures on GT-9100 + sKnownCodecList.put("OMX.SEC.avcdec", RANK_TESTED - 2); + sKnownCodecList.put("OMX.SEC.avc.sw.dec", RANK_SOFTWARE); + // Exynos 5 ? + sKnownCodecList.put("OMX.Exynos.avc.dec", RANK_TESTED); + sKnownCodecList.put("OMX.Exynos.AVC.Decoder", RANK_TESTED - 1); + + // ------ Huawei hisilicon ------ + // Kirin 910, Mali 450 MP + // Huawei HONOR 3C (H30-L01) + sKnownCodecList.put("OMX.k3.video.decoder.avc", RANK_TESTED); + // Kirin 920, Mali T624 + // Huawei HONOR 6 + sKnownCodecList.put("OMX.IMG.MSVDX.Decoder.AVC", RANK_TESTED); + + // ----- TI ----- + // TI OMAP4460 + // Galaxy Nexus + sKnownCodecList.put("OMX.TI.DUCATI1.VIDEO.DECODER", RANK_TESTED); + + // ------ RockChip ------ + // Youku TVBox + sKnownCodecList.put("OMX.rk.video_decoder.avc", RANK_TESTED); + + // ------ AMLogic ----- + // MiBox1, 1s, 2 + sKnownCodecList.put("OMX.amlogic.avc.decoder.awesome", RANK_TESTED); + + // ------ Marvell ------ + // Lenovo A788t + sKnownCodecList.put("OMX.MARVELL.VIDEO.HW.CODA7542DECODER", RANK_TESTED); + sKnownCodecList.put("OMX.MARVELL.VIDEO.H264DECODER", RANK_SOFTWARE); + + // ----- TODO: need test ----- + sKnownCodecList.remove("OMX.Action.Video.Decoder"); + sKnownCodecList.remove("OMX.allwinner.video.decoder.avc"); + sKnownCodecList.remove("OMX.BRCM.vc4.decoder.avc"); + sKnownCodecList.remove("OMX.brcm.video.h264.hw.decoder"); + sKnownCodecList.remove("OMX.brcm.video.h264.decoder"); + sKnownCodecList.remove("OMX.cosmo.video.decoder.avc"); + sKnownCodecList.remove("OMX.duos.h264.decoder"); + sKnownCodecList.remove("OMX.hantro.81x0.video.decoder"); + sKnownCodecList.remove("OMX.hantro.G1.video.decoder"); + sKnownCodecList.remove("OMX.hisi.video.decoder"); + sKnownCodecList.remove("OMX.LG.decoder.video.avc"); + sKnownCodecList.remove("OMX.MS.AVC.Decoder"); + sKnownCodecList.remove("OMX.RENESAS.VIDEO.DECODER.H264"); + sKnownCodecList.remove("OMX.RTK.video.decoder"); + sKnownCodecList.remove("OMX.sprd.h264.decoder"); + sKnownCodecList.remove("OMX.ST.VFM.H264Dec"); + sKnownCodecList.remove("OMX.vpu.video_decoder.avc"); + sKnownCodecList.remove("OMX.WMT.decoder.avc"); + + // Really ? + sKnownCodecList.remove("OMX.bluestacks.hw.decoder"); + + // --------------- + // Useless codec + // ----- google ----- + sKnownCodecList.put("OMX.google.h264.decoder", RANK_TESTED); + sKnownCodecList.put("OMX.google.h264.lc.decoder", RANK_SOFTWARE); + // ----- huawei k920 ----- + sKnownCodecList.put("OMX.k3.ffmpeg.decoder", RANK_SOFTWARE); + sKnownCodecList.put("OMX.ffmpeg.video.decoder", RANK_SOFTWARE); + // ----- unknown ----- + sKnownCodecList.put("OMX.sprd.soft.h264.decoder", RANK_SOFTWARE); + + return sKnownCodecList; + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public static IjkMediaCodecInfo setupCandidate(MediaCodecInfo codecInfo, + String mimeType) { + if (codecInfo == null + || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + return null; + + String name = codecInfo.getName(); + if (TextUtils.isEmpty(name)) + return null; + + name = name.toLowerCase(Locale.US); + int rank = RANK_NO_SENSE; + if (!name.startsWith("omx.")) { + rank = RANK_NON_STANDARD; + } else if (name.startsWith("omx.pv")) { + rank = RANK_SOFTWARE; + } else if (false && name.startsWith("omx.google.")) { + rank = RANK_SOFTWARE; + } else if (name.startsWith("omx.ffmpeg.")) { + rank = RANK_SOFTWARE; + } else if (name.startsWith("omx.k3.ffmpeg.")) { + rank = RANK_SOFTWARE; + } else if (name.startsWith("omx.avcodec.")) { + rank = RANK_SOFTWARE; + } else if (name.startsWith("omx.ittiam.")) { + // unknown codec in qualcomm SoC + rank = RANK_NO_SENSE; + } else if (name.startsWith("omx.mtk.")) { + // 1. MTK only works on 4.3 and above + // 2. MTK works on MIUI 6 (4.2.1) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) + rank = RANK_NO_SENSE; + else + rank = RANK_TESTED; + } else { + Integer knownRank = getKnownCodecList().get(name); + if (knownRank != null) { + rank = knownRank; + } else { + try { + CodecCapabilities cap = codecInfo + .getCapabilitiesForType(mimeType); + if (cap != null) + rank = RANK_ACCEPTABLE; + else + rank = RANK_LAST_CHANCE; + } catch (Throwable e) { + rank = RANK_LAST_CHANCE; + } + } + } + + IjkMediaCodecInfo candidate = new IjkMediaCodecInfo(); + candidate.mCodecInfo = codecInfo; + candidate.mRank = rank; + candidate.mMimeType = mimeType; + return candidate; + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public void dumpProfileLevels(String mimeType) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + return; + + try { + CodecCapabilities caps = mCodecInfo + .getCapabilitiesForType(mimeType); + int maxProfile = 0; + int maxLevel = 0; + if (caps != null) { + if (caps.profileLevels != null) { + for (CodecProfileLevel profileLevel : caps.profileLevels) { + if (profileLevel == null) + continue; + + maxProfile = Math.max(maxProfile, profileLevel.profile); + maxLevel = Math.max(maxLevel, profileLevel.level); + } + } + } + + Log.i(TAG, + String.format(Locale.US, "%s", + getProfileLevelName(maxProfile, maxLevel))); + } catch (Throwable e) { + Log.i(TAG, "profile-level: exception"); + } + } + + public static String getProfileLevelName(int profile, int level) { + return String.format(Locale.US, " %s Profile Level %s (%d,%d)", + getProfileName(profile), getLevelName(level), profile, level); + } + + public static String getProfileName(int profile) { + switch (profile) { + case CodecProfileLevel.AVCProfileBaseline: + return "Baseline"; + case CodecProfileLevel.AVCProfileMain: + return "Main"; + case CodecProfileLevel.AVCProfileExtended: + return "Extends"; + case CodecProfileLevel.AVCProfileHigh: + return "High"; + case CodecProfileLevel.AVCProfileHigh10: + return "High10"; + case CodecProfileLevel.AVCProfileHigh422: + return "High422"; + case CodecProfileLevel.AVCProfileHigh444: + return "High444"; + default: + return "Unknown"; + } + } + + public static String getLevelName(int level) { + switch (level) { + case CodecProfileLevel.AVCLevel1: + return "1"; + case CodecProfileLevel.AVCLevel1b: + return "1b"; + case CodecProfileLevel.AVCLevel11: + return "11"; + case CodecProfileLevel.AVCLevel12: + return "12"; + case CodecProfileLevel.AVCLevel13: + return "13"; + case CodecProfileLevel.AVCLevel2: + return "2"; + case CodecProfileLevel.AVCLevel21: + return "21"; + case CodecProfileLevel.AVCLevel22: + return "22"; + case CodecProfileLevel.AVCLevel3: + return "3"; + case CodecProfileLevel.AVCLevel31: + return "31"; + case CodecProfileLevel.AVCLevel32: + return "32"; + case CodecProfileLevel.AVCLevel4: + return "4"; + case CodecProfileLevel.AVCLevel41: + return "41"; + case CodecProfileLevel.AVCLevel42: + return "42"; + case CodecProfileLevel.AVCLevel5: + return "5"; + case CodecProfileLevel.AVCLevel51: + return "51"; + case 65536: // CodecProfileLevel.AVCLevel52: + return "52"; + default: + return "0"; + } + } +} diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaMeta.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaMeta.java new file mode 100644 index 0000000..d2ba5f6 --- /dev/null +++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaMeta.java @@ -0,0 +1,401 @@ +package tv.danmaku.ijk.media.player; + +import android.os.Bundle; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.Locale; + +@SuppressWarnings("SameParameterValue") +public class IjkMediaMeta { + // media meta + public static final String IJKM_KEY_FORMAT = "format"; + public static final String IJKM_KEY_DURATION_US = "duration_us"; + public static final String IJKM_KEY_START_US = "start_us"; + public static final String IJKM_KEY_BITRATE = "bitrate"; + public static final String IJKM_KEY_VIDEO_STREAM = "video"; + public static final String IJKM_KEY_AUDIO_STREAM = "audio"; + public static final String IJKM_KEY_TIMEDTEXT_STREAM = "timedtext"; + + // stream meta + public static final String IJKM_KEY_TYPE = "type"; + public static final String IJKM_VAL_TYPE__VIDEO = "video"; + public static final String IJKM_VAL_TYPE__AUDIO = "audio"; + public static final String IJKM_VAL_TYPE__TIMEDTEXT = "timedtext"; + public static final String IJKM_VAL_TYPE__UNKNOWN = "unknown"; + public static final String IJKM_KEY_LANGUAGE = "language"; + + public static final String IJKM_KEY_CODEC_NAME = "codec_name"; + public static final String IJKM_KEY_CODEC_PROFILE = "codec_profile"; + public static final String IJKM_KEY_CODEC_LEVEL = "codec_level"; + public static final String IJKM_KEY_CODEC_LONG_NAME = "codec_long_name"; + public static final String IJKM_KEY_CODEC_PIXEL_FORMAT = "codec_pixel_format"; + public static final String IJKM_KEY_CODEC_PROFILE_ID = "codec_profile_id"; + + // stream: video + public static final String IJKM_KEY_WIDTH = "width"; + public static final String IJKM_KEY_HEIGHT = "height"; + public static final String IJKM_KEY_FPS_NUM = "fps_num"; + public static final String IJKM_KEY_FPS_DEN = "fps_den"; + public static final String IJKM_KEY_TBR_NUM = "tbr_num"; + public static final String IJKM_KEY_TBR_DEN = "tbr_den"; + public static final String IJKM_KEY_SAR_NUM = "sar_num"; + public static final String IJKM_KEY_SAR_DEN = "sar_den"; + // stream: audio + public static final String IJKM_KEY_SAMPLE_RATE = "sample_rate"; + public static final String IJKM_KEY_CHANNEL_LAYOUT = "channel_layout"; + + public static final String IJKM_KEY_STREAMS = "streams"; + + public static final long AV_CH_FRONT_LEFT = 0x00000001; + public static final long AV_CH_FRONT_RIGHT = 0x00000002; + public static final long AV_CH_FRONT_CENTER = 0x00000004; + public static final long AV_CH_LOW_FREQUENCY = 0x00000008; + public static final long AV_CH_BACK_LEFT = 0x00000010; + public static final long AV_CH_BACK_RIGHT = 0x00000020; + public static final long AV_CH_FRONT_LEFT_OF_CENTER = 0x00000040; + public static final long AV_CH_FRONT_RIGHT_OF_CENTER = 0x00000080; + public static final long AV_CH_BACK_CENTER = 0x00000100; + public static final long AV_CH_SIDE_LEFT = 0x00000200; + public static final long AV_CH_SIDE_RIGHT = 0x00000400; + public static final long AV_CH_TOP_CENTER = 0x00000800; + public static final long AV_CH_TOP_FRONT_LEFT = 0x00001000; + public static final long AV_CH_TOP_FRONT_CENTER = 0x00002000; + public static final long AV_CH_TOP_FRONT_RIGHT = 0x00004000; + public static final long AV_CH_TOP_BACK_LEFT = 0x00008000; + public static final long AV_CH_TOP_BACK_CENTER = 0x00010000; + public static final long AV_CH_TOP_BACK_RIGHT = 0x00020000; + public static final long AV_CH_STEREO_LEFT = 0x20000000; + public static final long AV_CH_STEREO_RIGHT = 0x40000000; + public static final long AV_CH_WIDE_LEFT = 0x0000000080000000L; + public static final long AV_CH_WIDE_RIGHT = 0x0000000100000000L; + public static final long AV_CH_SURROUND_DIRECT_LEFT = 0x0000000200000000L; + public static final long AV_CH_SURROUND_DIRECT_RIGHT = 0x0000000400000000L; + public static final long AV_CH_LOW_FREQUENCY_2 = 0x0000000800000000L; + + public static final long AV_CH_LAYOUT_MONO = (AV_CH_FRONT_CENTER); + public static final long AV_CH_LAYOUT_STEREO = (AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT); + public static final long AV_CH_LAYOUT_2POINT1 = (AV_CH_LAYOUT_STEREO | AV_CH_LOW_FREQUENCY); + public static final long AV_CH_LAYOUT_2_1 = (AV_CH_LAYOUT_STEREO | AV_CH_BACK_CENTER); + public static final long AV_CH_LAYOUT_SURROUND = (AV_CH_LAYOUT_STEREO | AV_CH_FRONT_CENTER); + public static final long AV_CH_LAYOUT_3POINT1 = (AV_CH_LAYOUT_SURROUND | AV_CH_LOW_FREQUENCY); + public static final long AV_CH_LAYOUT_4POINT0 = (AV_CH_LAYOUT_SURROUND | AV_CH_BACK_CENTER); + public static final long AV_CH_LAYOUT_4POINT1 = (AV_CH_LAYOUT_4POINT0 | AV_CH_LOW_FREQUENCY); + public static final long AV_CH_LAYOUT_2_2 = (AV_CH_LAYOUT_STEREO + | AV_CH_SIDE_LEFT | AV_CH_SIDE_RIGHT); + public static final long AV_CH_LAYOUT_QUAD = (AV_CH_LAYOUT_STEREO + | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT); + public static final long AV_CH_LAYOUT_5POINT0 = (AV_CH_LAYOUT_SURROUND + | AV_CH_SIDE_LEFT | AV_CH_SIDE_RIGHT); + public static final long AV_CH_LAYOUT_5POINT1 = (AV_CH_LAYOUT_5POINT0 | AV_CH_LOW_FREQUENCY); + public static final long AV_CH_LAYOUT_5POINT0_BACK = (AV_CH_LAYOUT_SURROUND + | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT); + public static final long AV_CH_LAYOUT_5POINT1_BACK = (AV_CH_LAYOUT_5POINT0_BACK | AV_CH_LOW_FREQUENCY); + public static final long AV_CH_LAYOUT_6POINT0 = (AV_CH_LAYOUT_5POINT0 | AV_CH_BACK_CENTER); + public static final long AV_CH_LAYOUT_6POINT0_FRONT = (AV_CH_LAYOUT_2_2 + | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER); + public static final long AV_CH_LAYOUT_HEXAGONAL = (AV_CH_LAYOUT_5POINT0_BACK | AV_CH_BACK_CENTER); + public static final long AV_CH_LAYOUT_6POINT1 = (AV_CH_LAYOUT_5POINT1 | AV_CH_BACK_CENTER); + public static final long AV_CH_LAYOUT_6POINT1_BACK = (AV_CH_LAYOUT_5POINT1_BACK | AV_CH_BACK_CENTER); + public static final long AV_CH_LAYOUT_6POINT1_FRONT = (AV_CH_LAYOUT_6POINT0_FRONT | AV_CH_LOW_FREQUENCY); + public static final long AV_CH_LAYOUT_7POINT0 = (AV_CH_LAYOUT_5POINT0 + | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT); + public static final long AV_CH_LAYOUT_7POINT0_FRONT = (AV_CH_LAYOUT_5POINT0 + | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER); + public static final long AV_CH_LAYOUT_7POINT1 = (AV_CH_LAYOUT_5POINT1 + | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT); + public static final long AV_CH_LAYOUT_7POINT1_WIDE = (AV_CH_LAYOUT_5POINT1 + | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER); + public static final long AV_CH_LAYOUT_7POINT1_WIDE_BACK = (AV_CH_LAYOUT_5POINT1_BACK + | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER); + public static final long AV_CH_LAYOUT_OCTAGONAL = (AV_CH_LAYOUT_5POINT0 + | AV_CH_BACK_LEFT | AV_CH_BACK_CENTER | AV_CH_BACK_RIGHT); + public static final long AV_CH_LAYOUT_STEREO_DOWNMIX = (AV_CH_STEREO_LEFT | AV_CH_STEREO_RIGHT); + + public static final int FF_PROFILE_H264_CONSTRAINED = (1<<9); // 8+1; constraint_set1_flag + public static final int FF_PROFILE_H264_INTRA = (1<<11); // 8+3; constraint_set3_flag + + public static final int FF_PROFILE_H264_BASELINE = 66; + public static final int FF_PROFILE_H264_CONSTRAINED_BASELINE = (66|FF_PROFILE_H264_CONSTRAINED); + public static final int FF_PROFILE_H264_MAIN = 77; + public static final int FF_PROFILE_H264_EXTENDED = 88; + public static final int FF_PROFILE_H264_HIGH = 100; + public static final int FF_PROFILE_H264_HIGH_10 = 110; + public static final int FF_PROFILE_H264_HIGH_10_INTRA = (110|FF_PROFILE_H264_INTRA); + public static final int FF_PROFILE_H264_HIGH_422 = 122; + public static final int FF_PROFILE_H264_HIGH_422_INTRA = (122|FF_PROFILE_H264_INTRA); + public static final int FF_PROFILE_H264_HIGH_444 = 144; + public static final int FF_PROFILE_H264_HIGH_444_PREDICTIVE = 244; + public static final int FF_PROFILE_H264_HIGH_444_INTRA = (244|FF_PROFILE_H264_INTRA); + public static final int FF_PROFILE_H264_CAVLC_444 = 44; + + public Bundle mMediaMeta; + + public String mFormat; + public long mDurationUS; + public long mStartUS; + public long mBitrate; + + public final ArrayList mStreams = new ArrayList(); + public IjkStreamMeta mVideoStream; + public IjkStreamMeta mAudioStream; + + public String getString(String key) { + return mMediaMeta.getString(key); + } + + public int getInt(String key) { + return getInt(key, 0); + } + + public int getInt(String key, int defaultValue) { + String value = getString(key); + if (TextUtils.isEmpty(value)) + return defaultValue; + + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public long getLong(String key) { + return getLong(key, 0); + } + + public long getLong(String key, long defaultValue) { + String value = getString(key); + if (TextUtils.isEmpty(value)) + return defaultValue; + + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public ArrayList getParcelableArrayList(String key) { + return mMediaMeta.getParcelableArrayList(key); + } + + public String getDurationInline() { + long duration = mDurationUS + 5000; + long secs = duration / 1000000; + long mins = secs / 60; + secs %= 60; + long hours = mins / 60; + mins %= 60; + return String.format(Locale.US, "%02d:%02d:%02d", hours, mins, secs); + } + + public static IjkMediaMeta parse(Bundle mediaMeta) { + if (mediaMeta == null) + return null; + + IjkMediaMeta meta = new IjkMediaMeta(); + meta.mMediaMeta = mediaMeta; + + meta.mFormat = meta.getString(IJKM_KEY_FORMAT); + meta.mDurationUS = meta.getLong(IJKM_KEY_DURATION_US); + meta.mStartUS = meta.getLong(IJKM_KEY_START_US); + meta.mBitrate = meta.getLong(IJKM_KEY_BITRATE); + + int videoStreamIndex = meta.getInt(IJKM_KEY_VIDEO_STREAM, -1); + int audioStreamIndex = meta.getInt(IJKM_KEY_AUDIO_STREAM, -1); + int subtitleStreamIndex = meta.getInt(IJKM_KEY_TIMEDTEXT_STREAM, -1); + + ArrayList streams = meta + .getParcelableArrayList(IJKM_KEY_STREAMS); + if (streams == null) + return meta; + + int index = -1; + for (Bundle streamBundle : streams) { + index++; + + if (streamBundle == null) { + continue; + } + + IjkStreamMeta streamMeta = new IjkStreamMeta(index); + streamMeta.mMeta = streamBundle; + streamMeta.mType = streamMeta.getString(IJKM_KEY_TYPE); + streamMeta.mLanguage = streamMeta.getString(IJKM_KEY_LANGUAGE); + if (TextUtils.isEmpty(streamMeta.mType)) + continue; + + streamMeta.mCodecName = streamMeta.getString(IJKM_KEY_CODEC_NAME); + streamMeta.mCodecProfile = streamMeta + .getString(IJKM_KEY_CODEC_PROFILE); + streamMeta.mCodecLongName = streamMeta + .getString(IJKM_KEY_CODEC_LONG_NAME); + streamMeta.mBitrate = streamMeta.getInt(IJKM_KEY_BITRATE); + + if (streamMeta.mType.equalsIgnoreCase(IJKM_VAL_TYPE__VIDEO)) { + streamMeta.mWidth = streamMeta.getInt(IJKM_KEY_WIDTH); + streamMeta.mHeight = streamMeta.getInt(IJKM_KEY_HEIGHT); + streamMeta.mFpsNum = streamMeta.getInt(IJKM_KEY_FPS_NUM); + streamMeta.mFpsDen = streamMeta.getInt(IJKM_KEY_FPS_DEN); + streamMeta.mTbrNum = streamMeta.getInt(IJKM_KEY_TBR_NUM); + streamMeta.mTbrDen = streamMeta.getInt(IJKM_KEY_TBR_DEN); + streamMeta.mSarNum = streamMeta.getInt(IJKM_KEY_SAR_NUM); + streamMeta.mSarDen = streamMeta.getInt(IJKM_KEY_SAR_DEN); + + if (videoStreamIndex == index) { + meta.mVideoStream = streamMeta; + } + } else if (streamMeta.mType.equalsIgnoreCase(IJKM_VAL_TYPE__AUDIO)) { + streamMeta.mSampleRate = streamMeta + .getInt(IJKM_KEY_SAMPLE_RATE); + streamMeta.mChannelLayout = streamMeta + .getLong(IJKM_KEY_CHANNEL_LAYOUT); + + if (audioStreamIndex == index) { + meta.mAudioStream = streamMeta; + } + } + meta.mStreams.add(streamMeta); + } + + return meta; + } + + public static class IjkStreamMeta { + public Bundle mMeta; + + public final int mIndex; + public String mType; + public String mLanguage; + + // common + public String mCodecName; + public String mCodecProfile; + public String mCodecLongName; + public long mBitrate; + + // video + public int mWidth; + public int mHeight; + public int mFpsNum; + public int mFpsDen; + public int mTbrNum; + public int mTbrDen; + public int mSarNum; + public int mSarDen; + + // audio + public int mSampleRate; + public long mChannelLayout; + + public IjkStreamMeta(int index) { + mIndex = index; + } + + public String getString(String key) { + return mMeta.getString(key); + } + + public int getInt(String key) { + return getInt(key, 0); + } + + public int getInt(String key, int defaultValue) { + String value = getString(key); + if (TextUtils.isEmpty(value)) + return defaultValue; + + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public long getLong(String key) { + return getLong(key, 0); + } + + public long getLong(String key, long defaultValue) { + String value = getString(key); + if (TextUtils.isEmpty(value)) + return defaultValue; + + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public String getCodecLongNameInline() { + if (!TextUtils.isEmpty(mCodecLongName)) { + return mCodecLongName; + } else if (!TextUtils.isEmpty(mCodecName)) { + return mCodecName; + } else { + return "N/A"; + } + } + + public String getCodecShortNameInline() { + if (!TextUtils.isEmpty(mCodecName)) { + return mCodecName; + } else { + return "N/A"; + } + } + + public String getResolutionInline() { + if (mWidth <= 0 || mHeight <= 0) { + return "N/A"; + } else if (mSarNum <= 0 || mSarDen <= 0) { + return String.format(Locale.US, "%d x %d", mWidth, mHeight); + } else { + return String.format(Locale.US, "%d x %d [SAR %d:%d]", mWidth, + mHeight, mSarNum, mSarDen); + } + } + + public String getFpsInline() { + if (mFpsNum <= 0 || mFpsDen <= 0) { + return "N/A"; + } else { + return String.valueOf(((float) (mFpsNum)) / mFpsDen); + } + } + + public String getBitrateInline() { + if (mBitrate <= 0) { + return "N/A"; + } else if (mBitrate < 1000) { + return String.format(Locale.US, "%d bit/s", mBitrate); + } else { + return String.format(Locale.US, "%d kb/s", mBitrate / 1000); + } + } + + public String getSampleRateInline() { + if (mSampleRate <= 0) { + return "N/A"; + } else { + return String.format(Locale.US, "%d Hz", mSampleRate); + } + } + + public String getChannelLayoutInline() { + if (mChannelLayout <= 0) { + return "N/A"; + } else { + if (mChannelLayout == AV_CH_LAYOUT_MONO) { + return "mono"; + } else if (mChannelLayout == AV_CH_LAYOUT_STEREO) { + return "stereo"; + } else { + return String.format(Locale.US, "%x", mChannelLayout); + } + } + } + } +} diff --git a/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java new file mode 100644 index 0000000..f48de47 --- /dev/null +++ b/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java @@ -0,0 +1,1365 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * 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; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.graphics.SurfaceTexture; +import android.graphics.Rect; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.PowerManager; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; + +import java.io.FileDescriptor; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.Locale; +import java.util.Map; + +import tv.danmaku.ijk.media.player.annotations.AccessedByNative; +import tv.danmaku.ijk.media.player.annotations.CalledByNative; +import tv.danmaku.ijk.media.player.misc.IIjkIOHttp; +import tv.danmaku.ijk.media.player.misc.IMediaDataSource; +import tv.danmaku.ijk.media.player.misc.ITrackInfo; +import tv.danmaku.ijk.media.player.misc.IjkTrackInfo; +import tv.danmaku.ijk.media.player.pragma.DebugLog; + +/** + * @author bbcallen + * + * Java wrapper of ffplay. + */ +public final class IjkMediaPlayer extends AbstractMediaPlayer { + + static { + sLocalLibLoader = new IjkLibLoader() { + @Override + public void loadLibrary(String libName) throws UnsatisfiedLinkError, SecurityException { + System.loadLibrary(libName); + } + }; + + IjkMediaPlayer.loadLibrariesOnce(null); + } + + private final static String TAG = "PRO"; + + private static final int MEDIA_NOP = 0; // interface test message + private static final int MEDIA_PREPARED = 1; + private static final int MEDIA_PLAYBACK_COMPLETE = 2; + private static final int MEDIA_BUFFERING_UPDATE = 3; + private static final int MEDIA_SEEK_COMPLETE = 4; + private static final int MEDIA_SET_VIDEO_SIZE = 5; + private static final int MEDIA_TIMED_TEXT = 99; + private static final int MEDIA_ERROR = 100; + private static final int MEDIA_INFO = 200; + + protected static final int MEDIA_SET_VIDEO_SAR = 10001; + + //---------------------------------------- + // options + public static final int IJK_LOG_UNKNOWN = 0; + public static final int IJK_LOG_DEFAULT = 1; + + public static final int IJK_LOG_VERBOSE = 2; + public static final int IJK_LOG_DEBUG = 3; + public static final int IJK_LOG_INFO = 4; + public static final int IJK_LOG_WARN = 5; + public static final int IJK_LOG_ERROR = 6; + public static final int IJK_LOG_FATAL = 7; + public static final int IJK_LOG_SILENT = 8; + + public static final int OPT_CATEGORY_FORMAT = 1; + public static final int OPT_CATEGORY_CODEC = 2; + public static final int OPT_CATEGORY_SWS = 3; + public static final int OPT_CATEGORY_PLAYER = 4; + + public static final int SDL_FCC_YV12 = 0x32315659; // YV12 + public static final int SDL_FCC_RV16 = 0x36315652; // RGB565 + public static final int SDL_FCC_RV32 = 0x32335652; // RGBX8888 + //---------------------------------------- + + //---------------------------------------- + // properties + public static final int PROP_FLOAT_VIDEO_DECODE_FRAMES_PER_SECOND = 10001; + public static final int PROP_FLOAT_VIDEO_OUTPUT_FRAMES_PER_SECOND = 10002; + public static final int FFP_PROP_FLOAT_PLAYBACK_RATE = 10003; + + public static final int FFP_PROP_INT64_SELECTED_VIDEO_STREAM = 20001; + public static final int FFP_PROP_INT64_SELECTED_AUDIO_STREAM = 20002; + public static final int FFP_PROP_INT64_SELECTED_TIMEDTEXT_STREAM = 20011; + + public static final int FFP_PROP_INT64_VIDEO_DECODER = 20003; + public static final int FFP_PROP_INT64_AUDIO_DECODER = 20004; + public static final int FFP_PROPV_DECODER_UNKNOWN = 0; + public static final int FFP_PROPV_DECODER_AVCODEC = 1; + public static final int FFP_PROPV_DECODER_MEDIACODEC = 2; + public static final int FFP_PROPV_DECODER_VIDEOTOOLBOX = 3; + public static final int FFP_PROP_INT64_VIDEO_CACHED_DURATION = 20005; + public static final int FFP_PROP_INT64_AUDIO_CACHED_DURATION = 20006; + public static final int FFP_PROP_INT64_VIDEO_CACHED_BYTES = 20007; + public static final int FFP_PROP_INT64_AUDIO_CACHED_BYTES = 20008; + public static final int FFP_PROP_INT64_VIDEO_CACHED_PACKETS = 20009; + public static final int FFP_PROP_INT64_AUDIO_CACHED_PACKETS = 20010; + public static final int FFP_PROP_INT64_ASYNC_STATISTIC_BUF_BACKWARDS = 20201; + public static final int FFP_PROP_INT64_ASYNC_STATISTIC_BUF_FORWARDS = 20202; + public static final int FFP_PROP_INT64_ASYNC_STATISTIC_BUF_CAPACITY = 20203; + public static final int FFP_PROP_INT64_TRAFFIC_STATISTIC_BYTE_COUNT = 20204; + public static final int FFP_PROP_INT64_CACHE_STATISTIC_PHYSICAL_POS = 20205; + public static final int FFP_PROP_INT64_CACHE_STATISTIC_BUF_FORWARDS = 20206; + public static final int FFP_PROP_INT64_CACHE_STATISTIC_FILE_POS = 20207; + public static final int FFP_PROP_INT64_CACHE_STATISTIC_COUNT_BYTES = 20208; + public static final int FFP_PROP_INT64_STATISTIC_COUNT_BYTES = 20209; + public static final int FFP_PROP_INT64_STATISTIC_COUNT_FRAMES = 20210; + + + public static final int FFP_PROP_INT64_BIT_RATE = 20100; + public static final int FFP_PROP_INT64_TCP_SPEED = 20200; + public static final int FFP_PROP_INT64_LATEST_SEEK_LOAD_DURATION = 20300; + //---------------------------------------- + + @AccessedByNative + private long mNativeMediaPlayer; + + @AccessedByNative + private long mNativeMediaDataSource; + + @AccessedByNative + private long mNativeIjkIOHttp; + + @AccessedByNative + private int mNativeSurfaceTexture; + + @AccessedByNative + private int mListenerContext; + + private SurfaceHolder mSurfaceHolder; + private EventHandler mEventHandler; + private PowerManager.WakeLock mWakeLock = null; + private boolean mScreenOnWhilePlaying; + private boolean mStayAwake; + + private int mVideoWidth; + private int mVideoHeight; + private int mVideoSarNum; + private int mVideoSarDen; + + private String mDataSource; + private final Context ctx; + + /** + * Default library loader + * Load them by yourself, if your libraries are not installed at default place. + */ + private static final IjkLibLoader sLocalLibLoader; + + private static volatile boolean mIsLibLoaded = false; + private int mAudioStream; + private Surface mSurface; + + public static void loadLibrariesOnce(IjkLibLoader libLoader) { + synchronized (IjkMediaPlayer.class) { + if (!mIsLibLoaded) { + if (libLoader == null) + libLoader = sLocalLibLoader; + + libLoader.loadLibrary("proffmpeg"); + libLoader.loadLibrary("prosdl"); + libLoader.loadLibrary("proplayer"); + mIsLibLoaded = true; + } + } + } + + private static volatile boolean mIsNativeInitialized = false; + + private static void initNativeOnce() { + synchronized (IjkMediaPlayer.class) { + if (!mIsNativeInitialized) { + native_init(); + mIsNativeInitialized = true; + } + } + } + + /** + * Default constructor. Consider using one of the create() methods for + * synchronously instantiating a IjkMediaPlayer from a Uri or resource. + *

+ * When done with the IjkMediaPlayer, you should call {@link #release()}, to + * free the resources. If not released, too many IjkMediaPlayer instances + * may result in an exception. + *

+ */ + public IjkMediaPlayer(Context context,String key) { + this(context, sLocalLibLoader, key); + } + + /** + * do not loadLibaray + * @param libLoader + * custom library loader, can be null. + */ + public IjkMediaPlayer(Context context, IjkLibLoader libLoader, String key) { + ctx = context; + initPlayer(libLoader, key); + } + + private void initPlayer(IjkLibLoader libLoader, String key) { + loadLibrariesOnce(libLoader); + initNativeOnce(); + + Looper looper; + + if ((looper = Looper.myLooper()) != null) { + mEventHandler = new EventHandler(this, looper); + } else if ((looper = Looper.getMainLooper()) != null) { + mEventHandler = new EventHandler(this, looper); + } else { + mEventHandler = null; + } + + /* + * Native setup requires a weak reference to our object. It's easier to + * create it here than in C++. + */ + try { + native_setup(ctx, new WeakReference(this), key); + } catch (Throwable ex) { + throw new IllegalStateException("Key不合法或已过期"); + } + +// native_setup(new WeakReference(this)); + } + + /* + * Update the IjkMediaPlayer SurfaceTexture. Call after setting a new + * display surface. + */ + private native void _setVideoSurface(Surface surface); + + /** + * Sets the {@link SurfaceHolder} to use for displaying the video portion of + * the media. + * + * Either a surface holder or surface must be set if a display or video sink + * is needed. Not calling this method or {@link #setSurface(Surface)} when + * playing back a video will result in only the audio track being played. A + * null surface holder or surface will result in only the audio track being + * played. + * + * @param sh + * the SurfaceHolder to use for video display + */ + @Override + public void setDisplay(SurfaceHolder sh) { + mSurfaceHolder = sh; + Surface surface; + + if (sh != null) { + surface = sh.getSurface(); + } else { + surface = null; + } + + _setVideoSurface(surface); + updateSurfaceScreenOn(); + } + + public boolean isSurfaceValid(){ + return mSurface != null || mSurfaceHolder != null; + } + + /** + * Sets the {@link Surface} to be used as the sink for the video portion of + * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but + * does not support {@link #setScreenOnWhilePlaying(boolean)}. Setting a + * Surface will un-set any Surface or SurfaceHolder that was previously set. + * A null surface will result in only the audio track being played. + * + * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps + * returned from {@link SurfaceTexture#getTimestamp()} will have an + * unspecified zero point. These timestamps cannot be directly compared + * between different media sources, different instances of the same media + * source, or multiple runs of the same program. The timestamp is normally + * monotonically increasing and is unaffected by time-of-day adjustments, + * but it is reset when the position is set. + * + * @param surface + * The {@link Surface} to be used for the video portion of the + * media. + */ + @Override + public void setSurface(Surface surface) { + if (mScreenOnWhilePlaying && surface != null) { + DebugLog.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface"); + } + + mSurfaceHolder = null; + _setVideoSurface(surface); + updateSurfaceScreenOn(); + mSurface = surface; + } + + /** + * Sets the data source as a content Uri. + * + * @param context the Context to use when resolving the Uri + * @param uri the Content URI of the data you want to play + * @throws IllegalStateException if it is called in an invalid state + */ + @Override + public void setDataSource(Context context, Uri uri) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException { + setDataSource(context, uri, null); + } + + /** + * Sets the data source as a content Uri. + * + * @param context the Context to use when resolving the Uri + * @param uri the Content URI of the data you want to play + * @param headers the headers to be sent together with the request for the data + * Note that the cross domain redirection is allowed by default, but that can be + * changed with key/value pairs through the headers parameter with + * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value + * to disallow or allow cross domain redirection. + * @throws IllegalStateException if it is called in an invalid state + */ + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + @Override + public void setDataSource(Context context, Uri uri, Map 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'