commit 6f71dbf2d3d186a36ea84b12778545a6cc9a728b Author: leo <2819748655@qq.com> Date: Sat Oct 7 21:31:31 2023 +0800 init project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5664a63 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/build +gen +.DS_Store +/obj +/ffmpeg_rtsp +.vscode +local.properties +gradle.properties +.gradle +.idea +*.iml +/exo-library diff --git a/EasyPlayerPro/.gitignore b/EasyPlayerPro/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/EasyPlayerPro/.gitignore @@ -0,0 +1 @@ +/build diff --git a/EasyPlayerPro/build.gradle b/EasyPlayerPro/build.gradle new file mode 100644 index 0000000..e3f84c9 --- /dev/null +++ b/EasyPlayerPro/build.gradle @@ -0,0 +1,97 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + buildToolsVersion '28.0.3' + + defaultConfig { + applicationId "org.easydarwin.easyplayer" + minSdkVersion 19 + targetSdkVersion 26 + versionCode 14210703 + versionName "1.4.21.0703" + + if (project.hasProperty('PLAYER_KEY')) { + buildConfigField 'String', 'PLAYER_KEY', PLAYER_KEY + } else { + println("NO RTMPKEY FOUND") + buildConfigField 'String', 'PLAYER_KEY', "\"EasyPlayer is free!\"" + } + + ndk { + //设置支持的SO库架构 +// abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a' + abiFilters 'x86', 'armeabi-v7a' + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + dataBinding { + enabled = true + } + + flavorDimensions "prod" + productFlavors { + pro { + applicationId "org.easydarwin.easyplayer.pro" + dimension "prod" + } + + /*fastPro { + applicationId "org.easydarwin.easyplayer.pro" + dimension "prod" + }*/ + + /*njjl { + applicationId "org.easydarwin.easyplayer.pro" + dimension "prod" + }*/ + } + + //签名配置 + signingConfigs { + + } + + android.applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = "EasyPlayerPro-${variant.versionCode}-${variant.versionName}.apk" + } + } +} + +repositories { + flatDir { + dirs 'libs' + } + + mavenCentral() +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + + implementation files('libs/gson-2.1.jar') + + implementation "com.android.support:appcompat-v7:${support_version}" + implementation "com.android.support:support-v4:${support_version}" + implementation "com.android.support:preference-v7:${support_version}" + implementation "com.android.support:design:${support_version}" + implementation "com.android.support:cardview-v7:${support_version}" + +// implementation 'com.writingminds:FFmpegAndroid:0.3.2' + implementation 'com.github.bumptech.glide:glide:3.7.0' + implementation 'com.github.chrisbanes:PhotoView:1.3.0' + implementation 'com.squareup.okhttp3:okhttp:3.4.1' + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + implementation 'com.budiyev.android:code-scanner:1.9.4' + + testImplementation 'junit:junit:4.12' + implementation project(':ijkplayer-java') +} diff --git a/EasyPlayerPro/libs/gson-2.1.jar b/EasyPlayerPro/libs/gson-2.1.jar new file mode 100644 index 0000000..b85f091 Binary files /dev/null and b/EasyPlayerPro/libs/gson-2.1.jar differ diff --git a/EasyPlayerPro/proguard-rules.pro b/EasyPlayerPro/proguard-rules.pro new file mode 100644 index 0000000..6b1dc55 --- /dev/null +++ b/EasyPlayerPro/proguard-rules.pro @@ -0,0 +1,30 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in D:\software\adt-bundle-windows-x86_64-20140702\sdk/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 *; +#} +# 忽略警告 +-ignorewarning + +-keepclassmembers class org.easydarwin.video.EasyRTSPClient { + public *; +} + +-keepclassmembers class org.easydarwin.video.RTSPClient { + private void onRTSPSourceCallBack(int, int, int, byte[], byte[]); +} +-keepclassmembers class org.easydarwin.video.RTSPClient$FrameInfo{ + *; +} \ No newline at end of file diff --git a/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/ApplicationTest.java b/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/ApplicationTest.java new file mode 100644 index 0000000..0d8aa35 --- /dev/null +++ b/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/ApplicationTest.java @@ -0,0 +1,13 @@ +package org.esaydarwin.rtsp.player; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/CameraToMpegTest.java b/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/CameraToMpegTest.java new file mode 100644 index 0000000..16089c7 --- /dev/null +++ b/EasyPlayerPro/src/androidTest/java/org/esaydarwin/rtsp/player/CameraToMpegTest.java @@ -0,0 +1,962 @@ +package org.esaydarwin.rtsp.player;/* + * Copyright 2013 The Android Open Source Project + * + * 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 android.annotation.TargetApi; +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaFormat; +import android.media.MediaMuxer; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLExt; +import android.opengl.EGLSurface; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.opengl.Matrix; +import android.os.Build; +import android.os.Environment; +import android.test.AndroidTestCase; +import android.util.Log; +import android.view.Surface; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +//20131106: removed unnecessary glFinish(), removed hard-coded "/sdcard" +//20131205: added alpha to EGLConfig +//20131210: demonstrate un-bind and re-bind of texture, for apps with shared EGL contexts +//20140123: correct error checks on glGet*Location() and program creation (they don't set error) + +/** + * Record video from the camera preview and encode it as an MP4 file. Demonstrates the use + * of MediaMuxer and MediaCodec with Camera input. Does not record audio. + *

+ * Generally speaking, it's better to use MediaRecorder for this sort of thing. This example + * demonstrates one possible advantage: editing of video as it's being encoded. A GLES 2.0 + * fragment shader is used to perform a silly color tweak every 15 frames. + *

+ * This uses various features first available in Android "Jellybean" 4.3 (API 18). There is + * no equivalent functionality in previous releases. (You can send the Camera preview to a + * byte buffer with a fully-specified format, but MediaCodec encoders want different input + * formats on different devices, and this use case wasn't well exercised in CTS pre-4.3.) + *

+ * The output file will be something like "/sdcard/test.640x480.mp4". + *

+ * (This was derived from bits and pieces of CTS tests, and is packaged as such, but is not + * currently part of CTS.) + */ +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +public class CameraToMpegTest extends AndroidTestCase { + private static final String TAG = "CameraToMpegTest"; + private static final boolean VERBOSE = false; // lots of logging + + // where to put the output file (note: /sdcard requires WRITE_EXTERNAL_STORAGE permission) + private static final File OUTPUT_DIR = Environment.getExternalStorageDirectory(); + + // parameters for the encoder + private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding + private static final int FRAME_RATE = 30; // 30fps + private static final int IFRAME_INTERVAL = 5; // 5 seconds between I-frames + private static final long DURATION_SEC = 8; // 8 seconds of video + + // Fragment shader that swaps color channels around. + private static final String SWAPPED_FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord).gbra;\n" + + "}\n"; + + // encoder / muxer state + private MediaCodec mEncoder; + private CodecInputSurface mInputSurface; + private MediaMuxer mMuxer; + private int mTrackIndex; + private boolean mMuxerStarted; + + // camera state + private Camera mCamera; + private SurfaceTextureManager mStManager; + + // allocate one of these up front so we don't need to do it every time + private MediaCodec.BufferInfo mBufferInfo; + + /** test entry point */ + public void testEncodeCameraToMp4() throws Throwable { + CameraToMpegWrapper.runTest(this); + } + + /** + * Wraps encodeCameraToMpeg(). This is necessary because SurfaceTexture will try to use + * the looper in the current thread if one exists, and the CTS tests create one on the + * test thread. + * + * The wrapper propagates exceptions thrown by the worker thread back to the caller. + */ + private static class CameraToMpegWrapper implements Runnable { + private Throwable mThrowable; + private CameraToMpegTest mTest; + + private CameraToMpegWrapper(CameraToMpegTest test) { + mTest = test; + } + + @Override + public void run() { + try { + mTest.encodeCameraToMpeg(); + } catch (Throwable th) { + mThrowable = th; + } + } + + /** Entry point. */ + public static void runTest(CameraToMpegTest obj) throws Throwable { + CameraToMpegWrapper wrapper = new CameraToMpegWrapper(obj); + Thread th = new Thread(wrapper, "codec test"); + th.start(); + th.join(); + if (wrapper.mThrowable != null) { + throw wrapper.mThrowable; + } + } + } + + /** + * Tests encoding of AVC video from Camera input. The output is saved as an MP4 file. + */ + private void encodeCameraToMpeg() { + // arbitrary but popular values + int encWidth = 640; + int encHeight = 480; + int encBitRate = 6000000; // Mbps + Log.d(TAG, MIME_TYPE + " output " + encWidth + "x" + encHeight + " @" + encBitRate); + + try { + prepareCamera(encWidth, encHeight); + prepareEncoder(encWidth, encHeight, encBitRate); + mInputSurface.makeCurrent(); + prepareSurfaceTexture(); + + mCamera.startPreview(); + + long startWhen = System.nanoTime(); + long desiredEnd = startWhen + DURATION_SEC * 1000000000L; + SurfaceTexture st = mStManager.getSurfaceTexture(); + int frameCount = 0; + + while (System.nanoTime() < desiredEnd) { + // Feed any pending encoder output into the muxer. + drainEncoder(false); + + // Switch up the colors every 15 frames. Besides demonstrating the use of + // fragment shaders for video editing, this provides a visual indication of + // the frame rate: if the camera is capturing at 15fps, the colors will change + // once per second. + if ((frameCount % 15) == 0) { + String fragmentShader = null; + if ((frameCount & 0x01) != 0) { + fragmentShader = SWAPPED_FRAGMENT_SHADER; + } + mStManager.changeFragmentShader(fragmentShader); + } + frameCount++; + + // Acquire a new frame of input, and render it to the Surface. If we had a + // GLSurfaceView we could switch EGL contexts and call drawImage() a second + // time to render it on screen. The texture can be shared between contexts by + // passing the GLSurfaceView's EGLContext as eglCreateContext()'s share_context + // argument. + mStManager.awaitNewImage(); + mStManager.drawImage(); + + // Set the presentation time stamp from the SurfaceTexture's time stamp. This + // will be used by MediaMuxer to set the PTS in the video. + if (VERBOSE) { + Log.d(TAG, "present: " + + ((st.getTimestamp() - startWhen) / 1000000.0) + "ms"); + } + mInputSurface.setPresentationTime(st.getTimestamp()); + + // Submit it to the encoder. The eglSwapBuffers call will block if the input + // is full, which would be bad if it stayed full until we dequeued an output + // buffer (which we can't do, since we're stuck here). So long as we fully drain + // the encoder before supplying additional input, the system guarantees that we + // can supply another frame without blocking. + if (VERBOSE) Log.d(TAG, "sending frame to encoder"); + mInputSurface.swapBuffers(); + } + + // send end-of-stream to encoder, and drain remaining output + drainEncoder(true); + } catch (IOException e) { + e.printStackTrace(); + } finally { + // release everything we grabbed + releaseCamera(); + releaseEncoder(); + releaseSurfaceTexture(); + } + } + + /** + * Configures Camera for video capture. Sets mCamera. + *

+ * Opens a Camera and sets parameters. Does not start preview. + */ + private void prepareCamera(int encWidth, int encHeight) { + if (mCamera != null) { + throw new RuntimeException("camera already initialized"); + } + + Camera.CameraInfo info = new Camera.CameraInfo(); + + // Try to find a front-facing camera (e.g. for videoconferencing). + int numCameras = Camera.getNumberOfCameras(); + for (int i = 0; i < numCameras; i++) { + Camera.getCameraInfo(i, info); + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + mCamera = Camera.open(i); + break; + } + } + if (mCamera == null) { + Log.d(TAG, "No front-facing camera found; opening default"); + mCamera = Camera.open(); // opens first back-facing camera + } + if (mCamera == null) { + throw new RuntimeException("Unable to open camera"); + } + + Camera.Parameters parms = mCamera.getParameters(); + + choosePreviewSize(parms, encWidth, encHeight); + // leave the frame rate set to default + mCamera.setParameters(parms); + + Camera.Size size = parms.getPreviewSize(); + Log.d(TAG, "Camera preview size is " + size.width + "x" + size.height); + } + + /** + * Attempts to find a preview size that matches the provided width and height (which + * specify the dimensions of the encoded video). If it fails to find a match it just + * uses the default preview size. + *

+ * TODO: should do a best-fit match. + */ + private static void choosePreviewSize(Camera.Parameters parms, int width, int height) { + // We should make sure that the requested MPEG size is less than the preferred + // size, and has the same aspect ratio. + Camera.Size ppsfv = parms.getPreferredPreviewSizeForVideo(); + if (VERBOSE && ppsfv != null) { + Log.d(TAG, "Camera preferred preview size for video is " + + ppsfv.width + "x" + ppsfv.height); + } + + for (Camera.Size size : parms.getSupportedPreviewSizes()) { + if (size.width == width && size.height == height) { + parms.setPreviewSize(width, height); + return; + } + } + + Log.w(TAG, "Unable to set preview size to " + width + "x" + height); + if (ppsfv != null) { + parms.setPreviewSize(ppsfv.width, ppsfv.height); + } + } + + /** + * Stops camera preview, and releases the camera to the system. + */ + private void releaseCamera() { + if (VERBOSE) Log.d(TAG, "releasing camera"); + if (mCamera != null) { + mCamera.stopPreview(); + mCamera.release(); + mCamera = null; + } + } + + /** + * Configures SurfaceTexture for camera preview. Initializes mStManager, and sets the + * associated SurfaceTexture as the Camera's "preview texture". + *

+ * Configure the EGL surface that will be used for output before calling here. + */ + private void prepareSurfaceTexture() { + mStManager = new SurfaceTextureManager(); + SurfaceTexture st = mStManager.getSurfaceTexture(); + try { + mCamera.setPreviewTexture(st); + } catch (IOException ioe) { + throw new RuntimeException("setPreviewTexture failed", ioe); + } + } + + /** + * Releases the SurfaceTexture. + */ + private void releaseSurfaceTexture() { + if (mStManager != null) { + mStManager.release(); + mStManager = null; + } + } + + /** + * Configures encoder and muxer state, and prepares the input Surface. Initializes + * mEncoder, mMuxer, mInputSurface, mBufferInfo, mTrackIndex, and mMuxerStarted. + */ + private void prepareEncoder(int width, int height, int bitRate) throws IOException { + mBufferInfo = new MediaCodec.BufferInfo(); + + MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height); + + // Set some properties. Failing to specify some of these can cause the MediaCodec + // configure() call to throw an unhelpful exception. + format.setInteger(MediaFormat.KEY_COLOR_FORMAT, + MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); + format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); + format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); + format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); + if (VERBOSE) Log.d(TAG, "format: " + format); + + // Create a MediaCodec encoder, and configure it with our format. Get a Surface + // we can use for input and wrap it with a class that handles the EGL work. + // + // If you want to have two EGL contexts -- one for display, one for recording -- + // you will likely want to defer instantiation of CodecInputSurface until after the + // "display" EGL context is created, then modify the eglCreateContext call to + // take eglGetCurrentContext() as the share_context argument. + mEncoder = MediaCodec.createEncoderByType(MIME_TYPE); + mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + mInputSurface = new CodecInputSurface(mEncoder.createInputSurface()); + mEncoder.start(); + + // Output filename. Ideally this would use Context.getFilesDir() rather than a + // hard-coded output directory. + String outputPath = new File(OUTPUT_DIR, + "test." + width + "x" + height + ".mp4").toString(); + Log.i(TAG, "Output file is " + outputPath); + + + // Create a MediaMuxer. We can't add the video track and start() the muxer here, + // because our MediaFormat doesn't have the Magic Goodies. These can only be + // obtained from the encoder after it has started processing data. + // + // We're not actually interested in multiplexing audio. We just want to convert + // the raw H.264 elementary stream we get from MediaCodec into a .mp4 file. + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); + } + } catch (IOException ioe) { + throw new RuntimeException("MediaMuxer creation failed", ioe); + } + + mTrackIndex = -1; + mMuxerStarted = false; + } + + /** + * Releases encoder resources. + */ + + private void releaseEncoder() { + if (VERBOSE) Log.d(TAG, "releasing encoder objects"); + if (mEncoder != null) { + mEncoder.stop(); + mEncoder.release(); + mEncoder = null; + } + if (mInputSurface != null) { + mInputSurface.release(); + mInputSurface = null; + } + if (mMuxer != null) { + mMuxer.stop(); + mMuxer.release(); + mMuxer = null; + } + } + + /** + * Extracts all pending data from the encoder and forwards it to the muxer. + *

+ * If endOfStream is not set, this returns when there is no more data to drain. If it + * is set, we send EOS to the encoder, and then iterate until we see EOS on the output. + * Calling this with endOfStream set should be done once, right before stopping the muxer. + *

+ * We're just using the muxer to get a .mp4 file (instead of a raw H.264 stream). We're + * not recording audio. + */ + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + private void drainEncoder(boolean endOfStream) { + final int TIMEOUT_USEC = 10000; + if (VERBOSE) Log.d(TAG, "drainEncoder(" + endOfStream + ")"); + + if (endOfStream) { + if (VERBOSE) Log.d(TAG, "sending EOS to encoder"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + mEncoder.signalEndOfInputStream(); + } + } + + ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers(); + while (true) { + int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); + if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { + // no output available yet + if (!endOfStream) { + break; // out of while + } else { + if (VERBOSE) Log.d(TAG, "no output available, spinning to await EOS"); + } + } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + // not expected for an encoder + encoderOutputBuffers = mEncoder.getOutputBuffers(); + } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + // should happen before receiving buffers, and should only happen once + if (mMuxerStarted) { + throw new RuntimeException("format changed twice"); + } + MediaFormat newFormat = mEncoder.getOutputFormat(); + Log.d(TAG, "encoder output format changed: " + newFormat); + + // now that we have the Magic Goodies, start the muxer + mTrackIndex = mMuxer.addTrack(newFormat); + mMuxer.start(); + mMuxerStarted = true; + } else if (encoderStatus < 0) { + Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: " + + encoderStatus); + // let's ignore it + } else { + ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; + if (encodedData == null) { + throw new RuntimeException("encoderOutputBuffer " + encoderStatus + + " was null"); + } + + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + // The codec config data was pulled out and fed to the muxer when we got + // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. + if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG"); + mBufferInfo.size = 0; + } + + if (mBufferInfo.size != 0) { + if (!mMuxerStarted) { + throw new RuntimeException("muxer hasn't started"); + } + + // adjust the ByteBuffer values to match BufferInfo (not needed?) + encodedData.position(mBufferInfo.offset); + encodedData.limit(mBufferInfo.offset + mBufferInfo.size); + + mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo); + if (VERBOSE) Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer"); + } + + mEncoder.releaseOutputBuffer(encoderStatus, false); + + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + if (!endOfStream) { + Log.w(TAG, "reached end of stream unexpectedly"); + } else { + if (VERBOSE) Log.d(TAG, "end of stream reached"); + } + break; // out of while + } + } + } + } + + + /** + * Holds state associated with a Surface used for MediaCodec encoder input. + *

+ * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses + * that to create an EGL window surface. Calls to eglSwapBuffers() cause a frame of data to + * be sent to the video encoder. + *

+ * This object owns the Surface -- releasing this will release the Surface too. + */ + private static class CodecInputSurface { + private static final int EGL_RECORDABLE_ANDROID = 0x3142; + + private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; + private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; + private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; + + private Surface mSurface; + + /** + * Creates a CodecInputSurface from a Surface. + */ + public CodecInputSurface(Surface surface) { + if (surface == null) { + throw new NullPointerException(); + } + mSurface = surface; + + eglSetup(); + } + + /** + * Prepares EGL. We want a GLES 2.0 context and a surface that supports recording. + */ + private void eglSetup() { + mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { + throw new RuntimeException("unable to get EGL14 display"); + } + int[] version = new int[2]; + if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { + throw new RuntimeException("unable to initialize EGL14"); + } + + // Configure EGL for recording and OpenGL ES 2.0. + int[] attribList = { + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_ALPHA_SIZE, 8, + EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, + EGL_RECORDABLE_ANDROID, 1, + EGL14.EGL_NONE + }; + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, + numConfigs, 0); + checkEglError("eglCreateContext RGB888+recordable ES2"); + + // Configure context for OpenGL ES 2.0. + int[] attrib_list = { + EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, + EGL14.EGL_NONE + }; + mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, + attrib_list, 0); + checkEglError("eglCreateContext"); + + // Create a window surface, and attach it to the Surface we received. + int[] surfaceAttribs = { + EGL14.EGL_NONE + }; + mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface, + surfaceAttribs, 0); + checkEglError("eglCreateWindowSurface"); + } + + /** + * Discards all resources held by this class, notably the EGL context. Also releases the + * Surface that was passed to our constructor. + */ + public void release() { + if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { + EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, + EGL14.EGL_NO_CONTEXT); + EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); + EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); + EGL14.eglReleaseThread(); + EGL14.eglTerminate(mEGLDisplay); + } + mSurface.release(); + + mEGLDisplay = EGL14.EGL_NO_DISPLAY; + mEGLContext = EGL14.EGL_NO_CONTEXT; + mEGLSurface = EGL14.EGL_NO_SURFACE; + + mSurface = null; + } + + /** + * Makes our EGL context and surface current. + */ + public void makeCurrent() { + EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext); + checkEglError("eglMakeCurrent"); + } + + /** + * Calls eglSwapBuffers. Use this to "publish" the current frame. + */ + public boolean swapBuffers() { + boolean result = EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface); + checkEglError("eglSwapBuffers"); + return result; + } + + /** + * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds. + */ + public void setPresentationTime(long nsecs) { + EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs); + checkEglError("eglPresentationTimeANDROID"); + } + + /** + * Checks for EGL errors. Throws an exception if one is found. + */ + private void checkEglError(String msg) { + int error; + if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { + throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); + } + } + } + + + /** + * Manages a SurfaceTexture. Creates SurfaceTexture and TextureRender objects, and provides + * functions that wait for frames and render them to the current EGL surface. + *

+ * The SurfaceTexture can be passed to Camera.setPreviewTexture() to receive camera output. + */ + private static class SurfaceTextureManager + implements SurfaceTexture.OnFrameAvailableListener { + private SurfaceTexture mSurfaceTexture; + private CameraToMpegTest.STextureRender mTextureRender; + + private Object mFrameSyncObject = new Object(); // guards mFrameAvailable + private boolean mFrameAvailable; + + /** + * Creates instances of TextureRender and SurfaceTexture. + */ + public SurfaceTextureManager() { + mTextureRender = new CameraToMpegTest.STextureRender(); + mTextureRender.surfaceCreated(); + + if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId()); + mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); + + // This doesn't work if this object is created on the thread that CTS started for + // these test cases. + // + // The CTS-created thread has a Looper, and the SurfaceTexture constructor will + // create a Handler that uses it. The "frame available" message is delivered + // there, but since we're not a Looper-based thread we'll never see it. For + // this to do anything useful, OutputSurface must be created on a thread without + // a Looper, so that SurfaceTexture uses the main application Looper instead. + // + // Java language note: passing "this" out of a constructor is generally unwise, + // but we should be able to get away with it here. + mSurfaceTexture.setOnFrameAvailableListener(this); + } + + public void release() { + // this causes a bunch of warnings that appear harmless but might confuse someone: + // W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned! + //mSurfaceTexture.release(); + + mTextureRender = null; + mSurfaceTexture = null; + } + + /** + * Returns the SurfaceTexture. + */ + public SurfaceTexture getSurfaceTexture() { + return mSurfaceTexture; + } + + /** + * Replaces the fragment shader. + */ + public void changeFragmentShader(String fragmentShader) { + mTextureRender.changeFragmentShader(fragmentShader); + } + + /** + * Latches the next buffer into the texture. Must be called from the thread that created + * the OutputSurface object. + */ + public void awaitNewImage() { + final int TIMEOUT_MS = 2500; + + synchronized (mFrameSyncObject) { + while (!mFrameAvailable) { + try { + // Wait for onFrameAvailable() to signal us. Use a timeout to avoid + // stalling the test if it doesn't arrive. + mFrameSyncObject.wait(TIMEOUT_MS); + if (!mFrameAvailable) { + // TODO: if "spurious wakeup", continue while loop + throw new RuntimeException("Camera frame wait timed out"); + } + } catch (InterruptedException ie) { + // shouldn't happen + throw new RuntimeException(ie); + } + } + mFrameAvailable = false; + } + + // Latch the data. + mTextureRender.checkGlError("before updateTexImage"); + mSurfaceTexture.updateTexImage(); + } + + /** + * Draws the data from SurfaceTexture onto the current EGL surface. + */ + public void drawImage() { + mTextureRender.drawFrame(mSurfaceTexture); + } + + @Override + public void onFrameAvailable(SurfaceTexture st) { + if (VERBOSE) Log.d(TAG, "new frame available"); + synchronized (mFrameSyncObject) { + if (mFrameAvailable) { + throw new RuntimeException("mFrameAvailable already set, frame could be dropped"); + } + mFrameAvailable = true; + mFrameSyncObject.notifyAll(); + } + } + } + + + /** + * Code for rendering a texture onto a surface using OpenGL ES 2.0. + */ + private static class STextureRender { + private static final int FLOAT_SIZE_BYTES = 4; + private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; + private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; + private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; + private final float[] mTriangleVerticesData = { + // X, Y, Z, U, V + -1.0f, -1.0f, 0, 0.f, 0.f, + 1.0f, -1.0f, 0, 1.f, 0.f, + -1.0f, 1.0f, 0, 0.f, 1.f, + 1.0f, 1.0f, 0, 1.f, 1.f, + }; + + private FloatBuffer mTriangleVertices; + + private static final String VERTEX_SHADER = + "uniform mat4 uMVPMatrix;\n" + + "uniform mat4 uSTMatrix;\n" + + "attribute vec4 aPosition;\n" + + "attribute vec4 aTextureCoord;\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " gl_Position = uMVPMatrix * aPosition;\n" + + " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + + "}\n"; + + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + // highp here doesn't seem to matter + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}\n"; + + private float[] mMVPMatrix = new float[16]; + private float[] mSTMatrix = new float[16]; + + private int mProgram; + private int mTextureID = -12345; + private int muMVPMatrixHandle; + private int muSTMatrixHandle; + private int maPositionHandle; + private int maTextureHandle; + + public STextureRender() { + mTriangleVertices = ByteBuffer.allocateDirect( + mTriangleVerticesData.length * FLOAT_SIZE_BYTES) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + mTriangleVertices.put(mTriangleVerticesData).position(0); + + Matrix.setIdentityM(mSTMatrix, 0); + } + + public int getTextureId() { + return mTextureID; + } + + public void drawFrame(SurfaceTexture st) { + checkGlError("onDrawFrame start"); + st.getTransformMatrix(mSTMatrix); + + // (optional) clear to green so we can see if we're failing to set pixels + GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); + + GLES20.glUseProgram(mProgram); + checkGlError("glUseProgram"); + + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); + + mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); + GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); + checkGlError("glVertexAttribPointer maPosition"); + GLES20.glEnableVertexAttribArray(maPositionHandle); + checkGlError("glEnableVertexAttribArray maPositionHandle"); + + mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); + GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); + checkGlError("glVertexAttribPointer maTextureHandle"); + GLES20.glEnableVertexAttribArray(maTextureHandle); + checkGlError("glEnableVertexAttribArray maTextureHandle"); + + Matrix.setIdentityM(mMVPMatrix, 0); + GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0); + GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0); + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + checkGlError("glDrawArrays"); + + // IMPORTANT: on some devices, if you are sharing the external texture between two + // contexts, one context may not see updates to the texture unless you un-bind and + // re-bind it. If you're not using shared EGL contexts, you don't need to bind + // texture 0 here. + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); + } + + /** + * Initializes GL state. Call this after the EGL surface has been created and made current. + */ + public void surfaceCreated() { + mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER); + if (mProgram == 0) { + throw new RuntimeException("failed creating program"); + } + maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); + checkLocation(maPositionHandle, "aPosition"); + maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); + checkLocation(maTextureHandle, "aTextureCoord"); + + muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); + checkLocation(muMVPMatrixHandle, "uMVPMatrix"); + muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix"); + checkLocation(muSTMatrixHandle, "uSTMatrix"); + + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + + mTextureID = textures[0]; + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); + checkGlError("glBindTexture mTextureID"); + + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_NEAREST); + GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, + GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, + GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, + GLES20.GL_CLAMP_TO_EDGE); + checkGlError("glTexParameter"); + } + + /** + * Replaces the fragment shader. Pass in null to reset to default. + */ + public void changeFragmentShader(String fragmentShader) { + if (fragmentShader == null) { + fragmentShader = FRAGMENT_SHADER; + } + GLES20.glDeleteProgram(mProgram); + mProgram = createProgram(VERTEX_SHADER, fragmentShader); + if (mProgram == 0) { + throw new RuntimeException("failed creating program"); + } + } + + private int loadShader(int shaderType, String source) { + int shader = GLES20.glCreateShader(shaderType); + checkGlError("glCreateShader type=" + shaderType); + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + int[] compiled = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == 0) { + Log.e(TAG, "Could not compile shader " + shaderType + ":"); + Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + shader = 0; + } + return shader; + } + + private int createProgram(String vertexSource, String fragmentSource) { + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); + if (vertexShader == 0) { + return 0; + } + int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); + if (pixelShader == 0) { + return 0; + } + + int program = GLES20.glCreateProgram(); + if (program == 0) { + Log.e(TAG, "Could not create program"); + } + GLES20.glAttachShader(program, vertexShader); + checkGlError("glAttachShader"); + GLES20.glAttachShader(program, pixelShader); + checkGlError("glAttachShader"); + GLES20.glLinkProgram(program); + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + Log.e(TAG, "Could not link program: "); + Log.e(TAG, GLES20.glGetProgramInfoLog(program)); + GLES20.glDeleteProgram(program); + program = 0; + } + return program; + } + + public void checkGlError(String op) { + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + Log.e(TAG, op + ": glError " + error); + throw new RuntimeException(op + ": glError " + error); + } + } + + public static void checkLocation(int location, String label) { + if (location < 0) { + throw new RuntimeException("Unable to locate '" + label + "' in program"); + } + } + } +} \ No newline at end of file diff --git a/EasyPlayerPro/src/main/AndroidManifest.xml b/EasyPlayerPro/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ed4da98 --- /dev/null +++ b/EasyPlayerPro/src/main/AndroidManifest.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/ic_launcher-web.png b/EasyPlayerPro/src/main/ic_launcher-web.png new file mode 100644 index 0000000..f9f47cf Binary files /dev/null and b/EasyPlayerPro/src/main/ic_launcher-web.png differ diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/AboutActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/AboutActivity.java new file mode 100644 index 0000000..1a25e86 --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/AboutActivity.java @@ -0,0 +1,98 @@ +package org.easydarwin.easyplayer; + +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.graphics.Color; +import android.net.Uri; +import android.os.Build; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.view.View; + +import org.easydarwin.easyplayer.databinding.ActivityAboutBinding; +import org.easydarwin.easyplayer.views.ProVideoView; + +public class AboutActivity extends AppCompatActivity implements View.OnClickListener { + + private ActivityAboutBinding binding; + + @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); + } + + binding = DataBindingUtil.setContentView(this, R.layout.activity_about); + + setSupportActionBar(binding.toolbar); + binding.toolbarBack.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + + // 版本信息 + binding.version.setText("EasyPlayer Pro播放器"); + binding.version.append("("); + + long activeDays = ProVideoView.getActiveDays(this,BuildConfig.PLAYER_KEY); + + SpannableString ss; + if (activeDays >= 9999) { + ss = new SpannableString("激活码永久有效"); + ss.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorGREEN)), + 0, + ss.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } else if (activeDays > 0) { + ss = new SpannableString(String.format("激活码还剩%d天可用", activeDays)); + ss.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorYELLOW)), + 0, + ss.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } else { + ss = new SpannableString(String.format("激活码已过期(%d)", activeDays)); + ss.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorRED)), + 0, + ss.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + + binding.version.append(ss); + binding.version.append(")"); + + binding.darwinContentTv.setOnClickListener(this); + binding.dssContentTv.setOnClickListener(this); + binding.nvrContentTv.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + Intent intent= new Intent(); + intent.setAction("android.intent.action.VIEW"); + Uri content_url = Uri.parse("http://www.easydarwin.org"); + + switch (v.getId()) { + case R.id.darwin_content_tv: + content_url = Uri.parse("http://www.easydarwin.org"); + break; + case R.id.dss_content_tv: + content_url = Uri.parse("http://www.easydss.com"); + break; + case R.id.nvr_content_tv: + content_url = Uri.parse("http://www.easynvr.com"); + break; + } + + intent.setData(content_url); + startActivity(intent); + } +} diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/MediaFilesActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/MediaFilesActivity.java new file mode 100755 index 0000000..839bbb9 --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/MediaFilesActivity.java @@ -0,0 +1,84 @@ +package org.easydarwin.easyplayer; + +import android.databinding.DataBindingUtil; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; +import android.view.MenuItem; +import android.view.View; + +import org.easydarwin.easyplayer.databinding.ActivityMediaFilesBinding; +import org.easydarwin.easyplayer.fragments.LocalFileFragment; + +/** + * 录像和截图 + * */ +public class MediaFilesActivity extends AppCompatActivity { + + private ActivityMediaFilesBinding mDataBinding; + + @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); + } + + mDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_media_files); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + mDataBinding.toolbarBack.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + + mDataBinding.viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) { + @Override + public int getCount() { + return 2; + } + + public Fragment getItem(int position) { + Bundle args = new Bundle(); + args.putBoolean(LocalFileFragment.KEY_IS_RECORD, position == 1); + return Fragment.instantiate(MediaFilesActivity.this, LocalFileFragment.class.getName(), args); + } + + @Override + public CharSequence getPageTitle(int position) { + String text = position == 0 ? "抓拍" : "录像"; + SpannableStringBuilder ssb = new SpannableStringBuilder(text); + + ForegroundColorSpan fcs = new ForegroundColorSpan(getResources().getColor(R.color.colorTheme2));//字体颜色设置为绿色 + ssb.setSpan(fcs, 0, ssb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);//设置字体颜色 + ssb.setSpan(new RelativeSizeSpan(1f), 0, ssb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + return ssb; + } + }); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + + return super.onOptionsItemSelected(item); + } +} diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/PlayListActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/PlayListActivity.java new file mode 100644 index 0000000..0e8a460 --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/PlayListActivity.java @@ -0,0 +1,417 @@ +package org.easydarwin.easyplayer; + +import android.Manifest; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.databinding.DataBindingUtil; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.signature.StringSignature; + +import org.easydarwin.easyplayer.data.VideoSource; +import org.easydarwin.easyplayer.databinding.ActivityPlayListBinding; +import org.easydarwin.easyplayer.databinding.VideoSourceItemBinding; +import org.easydarwin.easyplayer.util.FileUtil; +import org.easydarwin.easyplayer.util.SPUtil; +import org.easydarwin.easyplayer.views.ProVideoView; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +/** + * 视频广场 + * */ +public class PlayListActivity extends AppCompatActivity implements View.OnClickListener, View.OnLongClickListener { + + private static final int REQUEST_PLAY = 1000; + public static final int REQUEST_CAMERA_PERMISSION = 1001; + private static final int REQUEST_SCAN_TEXT_URL = 1003; // 扫描二维码 + + public static final String EXTRA_BOOLEAN_SELECT_ITEM_TO_PLAY = "extra-boolean-select-item-to-play"; + + private int mPos; + private ActivityPlayListBinding mBinding; + private RecyclerView mRecyclerView; + private EditText edit; + + private Cursor mCursor; + + private long mExitTime;//声明一个long类型变量:用于存放上一点击“返回键”的时刻 + + @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 = DataBindingUtil.setContentView(this, R.layout.activity_play_list); + + setSupportActionBar(mBinding.toolbar); + notifyAboutColorChange(); + + // 添加默认地址 + mCursor = TheApp.sDB.query(VideoSource.TABLE_NAME, null, null, null, null, null, null); + if (!mCursor.moveToFirst()) { + List urls = new ArrayList<>(); + urls.add("rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov"); + urls.add("rtmp://live.hkstv.hk.lxdns.com/live/hks2"); + urls.add("http://www.easydarwin.org/public/video/3/video.m3u8"); + urls.add("http://m4.pptvyun.com/pvod/e11a0/ijblO6coKRX6a8NEQgg8LDZcqPY/eyJkbCI6MTUxNjYyNTM3NSwiZXMiOjYwNDgwMCwiaWQiOiIwYTJkbnEtWG82S2VvcTZMNEsyZG9hZmhvNkNjbTY2WXB3IiwidiI6IjEuMCJ9/0a2dnq-Xo6Keoq6L4K2doafho6Ccm66Ypw.mp4"); + + for (String url : urls) { + ContentValues cv = new ContentValues(); + cv.put(VideoSource.URL, url); + TheApp.sDB.insert(VideoSource.TABLE_NAME, null, cv); + + mCursor.close(); + mCursor = TheApp.sDB.query(VideoSource.TABLE_NAME, null, null, null, null, null, null); + } + + SPUtil.setMediaCodec(this, true); + SPUtil.setUDPMode(this, false); + } + + mRecyclerView = mBinding.recycler; + mRecyclerView.setHasFixedSize(true); + mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + mRecyclerView.setAdapter(new RecyclerView.Adapter() { + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new PlayListViewHolder((VideoSourceItemBinding) DataBindingUtil.inflate(getLayoutInflater(), R.layout.video_source_item, parent, false)); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + PlayListViewHolder plvh = (PlayListViewHolder) holder; + mCursor.moveToPosition(position); + String name = mCursor.getString(mCursor.getColumnIndex(VideoSource.NAME)); + String url = mCursor.getString(mCursor.getColumnIndex(VideoSource.URL)); + + if (!TextUtils.isEmpty(name)) { + plvh.mTextView.setText(name); + } else { + plvh.mTextView.setText(url); + } + + File file = FileUtil.getSnapshotFile(url); + + Glide.with(PlayListActivity.this) + .load(file) + .signature(new StringSignature(UUID.randomUUID().toString())) + .placeholder(R.drawable.placeholder) + .centerCrop() + .into(plvh.mImageView); + + int audienceNumber = mCursor.getInt(mCursor.getColumnIndex(VideoSource.AUDIENCE_NUMBER)); + + if (audienceNumber > 0) { + plvh.mAudienceNumber.setText(String.format("当前观看人数:%d", audienceNumber)); + plvh.mAudienceNumber.setVisibility(View.VISIBLE); + } else { + plvh.mAudienceNumber.setVisibility(View.GONE); + } + } + + @Override + public int getItemCount() { + return mCursor.getCount(); + } + }); + + // 如果当前进程挂起,则进入启动页 + if (savedInstanceState == null) { + if (!getIntent().getBooleanExtra(EXTRA_BOOLEAN_SELECT_ITEM_TO_PLAY, false)) { + startActivity(new Intent(this, SplashActivity.class)); + } + } + + mBinding.pullToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + mBinding.pullToRefresh.setRefreshing(false); + } + }); + + mBinding.toolbarSetting.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startActivity(new Intent(PlayListActivity.this, SettingsActivity.class)); + } + }); + + mBinding.toolbarAdd.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + displayDialog(-1); + } + }); + + mBinding.toolbarAbout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startActivity(new Intent(PlayListActivity.this, AboutActivity.class)); + } + }); + } + + @Override + protected void onDestroy() { + mCursor.close(); + super.onDestroy(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + switch (requestCode) { + case REQUEST_CAMERA_PERMISSION: { + if (grantResults.length > 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) { + toScanQRActivity(); + } + + break; + } + } + } + + @Override + public void onClick(View view) { + PlayListViewHolder holder = (PlayListViewHolder) view.getTag(); + int pos = holder.getAdapterPosition(); + + if (pos != -1) { + mCursor.moveToPosition(pos); + String playUrl = mCursor.getString(mCursor.getColumnIndex(VideoSource.URL)); + + if (!TextUtils.isEmpty(playUrl)) { + mPos = pos; + + Intent i = new Intent(PlayListActivity.this, ProVideoActivity.class); + i.putExtra("videoPath", playUrl); + startActivityForResult(i, 999); + } + } + } + + @Override + public boolean onLongClick(View view) { + PlayListViewHolder holder = (PlayListViewHolder) view.getTag(); + final int pos = holder.getAdapterPosition(); + + if (pos != -1) { + new AlertDialog.Builder(this).setItems(new CharSequence[]{"修改", "删除"}, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + if (i == 0) { + displayDialog(pos); + } else { + new AlertDialog + .Builder(PlayListActivity.this) + .setMessage("确定要删除该地址吗?") + .setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + mCursor.moveToPosition(pos); + TheApp.sDB.delete(VideoSource.TABLE_NAME, VideoSource._ID + "=?", new String[]{String.valueOf(mCursor.getInt(mCursor.getColumnIndex(VideoSource._ID)))}); + mCursor.close(); + mCursor = TheApp.sDB.query(VideoSource.TABLE_NAME, null, null, null, null, null, null); + mRecyclerView.getAdapter().notifyItemRemoved(pos); + } + }) + .setNegativeButton("取消", null) + .show(); + } + } + }).show(); + } + + return true; + } + + @Override + public void onBackPressed() { + //与上次点击返回键时刻作差 + if ((System.currentTimeMillis() - mExitTime) > 2000) { + //大于2000ms则认为是误操作,使用Toast进行提示 + Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show(); + //并记录下本次点击“返回键”的时刻,以便下次进行判断 + mExitTime = System.currentTimeMillis(); + } else { + super.onBackPressed(); + } + } + + private void displayDialog(final int pos) { + String url = ""; + if (pos > -1) { + mCursor.moveToPosition(pos); + url = mCursor.getString(mCursor.getColumnIndex(VideoSource.URL)); + } + + View view = getLayoutInflater().inflate(R.layout.new_media_source_dialog, null); + edit = view.findViewById(R.id.new_media_source_url); + edit.setText(url); + edit.setSelection(url.length()); + + // 去扫描二维码 + final ImageButton btn = view.findViewById(R.id.new_media_scan); + btn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // 动态获取camera和audio权限 + if (ActivityCompat.checkSelfPermission(PlayListActivity.this, Manifest.permission.CAMERA) != PERMISSION_GRANTED + || ActivityCompat.checkSelfPermission(PlayListActivity.this, Manifest.permission.RECORD_AUDIO) != PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(PlayListActivity.this, new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}, REQUEST_CAMERA_PERMISSION); + } else { + toScanQRActivity(); + } + } + }); + + final AlertDialog dlg = new AlertDialog.Builder(PlayListActivity.this) + .setView(view) + .setTitle("请输入播放地址") + .setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + String url = String.valueOf(edit.getText()); + + if (TextUtils.isEmpty(url)) { + return; + } + + if (url.toLowerCase().indexOf("rtsp://") != 0 && url.toLowerCase().indexOf("rtmp://") != 0 && + url.toLowerCase().indexOf("http://") != 0 && url.toLowerCase().indexOf("https://") != 0 && + url.toLowerCase().indexOf("hls://") != 0) { + Toast.makeText(PlayListActivity.this,"不是合法的地址,请重新添加.",Toast.LENGTH_SHORT).show(); + return; + } + + ContentValues cv = new ContentValues(); + cv.put(VideoSource.URL, url); + + if (pos > -1) { + final int _id = mCursor.getInt(mCursor.getColumnIndex(VideoSource._ID)); + TheApp.sDB.update(VideoSource.TABLE_NAME, cv, VideoSource._ID + "=?", new String[]{String.valueOf(_id)}); + + mCursor.close(); + mCursor = TheApp.sDB.query(VideoSource.TABLE_NAME, null, null, null, null, null, null); + mRecyclerView.getAdapter().notifyItemChanged(pos); + } else { + TheApp.sDB.insert(VideoSource.TABLE_NAME, null, cv); + + mCursor.close(); + mCursor = TheApp.sDB.query(VideoSource.TABLE_NAME, null, null, null, null, null, null); + mRecyclerView.getAdapter().notifyItemInserted(mCursor.getCount() - 1); + } + } + }) + .setNegativeButton("取消", null) + .create(); + + dlg.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialogInterface) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(edit, InputMethodManager.SHOW_IMPLICIT); + } + }); + + dlg.show(); + } + + private void toScanQRActivity() { + Intent intent = new Intent(PlayListActivity.this, ScanQRActivity.class); + startActivityForResult(intent, REQUEST_SCAN_TEXT_URL); + overridePendingTransition(R.anim.slide_bottom_in, R.anim.slide_top_out); + } + + /* + * 显示key有限期 + * */ + private void notifyAboutColorChange() { + //// !!!! important to set KEY !!!! + ProVideoView.setKey(BuildConfig.PLAYER_KEY); + long activeDays = ProVideoView.getActiveDays(this,BuildConfig.PLAYER_KEY); + + ImageView iv = findViewById(R.id.toolbar_about); + + if (activeDays >= 9999) { + iv.setImageResource(R.drawable.new_version1); + } else if (activeDays > 0) { + iv.setImageResource(R.drawable.new_version2); + } else { + iv.setImageResource(R.drawable.new_version3); + } + } + + public void fileList(View view) { + Intent i = new Intent(this, MediaFilesActivity.class); + startActivity(i); + } + + /** + * 视频源的item + * */ + class PlayListViewHolder extends RecyclerView.ViewHolder { + private final TextView mTextView; + private final TextView mAudienceNumber; + private final ImageView mImageView; + + public PlayListViewHolder(VideoSourceItemBinding binding) { + super(binding.getRoot()); + + mTextView = binding.videoSourceItemName; + mAudienceNumber = binding.videoSourceItemAudienceNumber; + mImageView = binding.videoSourceItemThumb; + + itemView.setOnClickListener(PlayListActivity.this); + itemView.setOnLongClickListener(PlayListActivity.this); + itemView.setTag(this); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == REQUEST_SCAN_TEXT_URL) { + if (resultCode == RESULT_OK) { + String url = data.getStringExtra("text"); + edit.setText(url); + } + } else { +// mRecyclerView.getAdapter().notifyItemChanged(mPos); + mRecyclerView.getAdapter().notifyDataSetChanged(); + } + } +} diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ProVideoActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ProVideoActivity.java new file mode 100644 index 0000000..0253435 --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ProVideoActivity.java @@ -0,0 +1,474 @@ +package org.easydarwin.easyplayer; + +/* + * 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 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.databinding.DataBindingUtil; +import android.graphics.Color; +import android.media.MediaScannerConnection; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewConfigurationCompat; +import android.support.v7.app.AppCompatActivity; +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.TextView; +import android.widget.Toast; + +import com.bumptech.glide.Glide; + +import org.easydarwin.easyplayer.databinding.ActivityMainProBinding; +import org.easydarwin.easyplayer.util.FileUtil; +import org.easydarwin.easyplayer.util.SPUtil; +import org.easydarwin.easyplayer.views.ProVideoView; +import org.easydarwin.easyplayer.views.VideoControllerView; + +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; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; + +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 = DataBindingUtil.setContentView(this, R.layout.activity_main_pro); + + // 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 = findViewById(android.R.id.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; + + TextView view = (TextView) findViewById(R.id.loading_speed); + view.setText(String.format("%3.01fKB/s", received * 1.0f / 1024)); + + if (findViewById(android.R.id.progress).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/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ScanQRActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ScanQRActivity.java new file mode 100644 index 0000000..0f33f2c --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/ScanQRActivity.java @@ -0,0 +1,83 @@ +package org.easydarwin.easyplayer; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.view.View; + +import com.budiyev.android.codescanner.CodeScanner; +import com.budiyev.android.codescanner.CodeScannerView; +import com.budiyev.android.codescanner.DecodeCallback; +import com.budiyev.android.codescanner.ScanMode; +import com.google.zxing.Result; + +public class ScanQRActivity extends AppCompatActivity { + + private CodeScanner mCodeScanner; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_scan_qr); + + CodeScannerView scannerView = findViewById(R.id.scanner_view); + + mCodeScanner = new CodeScanner(this, scannerView); + mCodeScanner.setScanMode(ScanMode.SINGLE); + mCodeScanner.setAutoFocusEnabled(true); + mCodeScanner.setDecodeCallback(new DecodeCallback() { + @Override + public void onDecoded(@NonNull final Result result) { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (!TextUtils.isEmpty(result.getText())) { + final String text = result.getText().trim(); + + if (text.toLowerCase().startsWith("rtsp://") || + text.toLowerCase().startsWith("rtmp://") || + text.toLowerCase().startsWith("http://") || + text.toLowerCase().startsWith("https://") || + text.toLowerCase().startsWith("hls://")) { + Intent intent = new Intent(); + intent.putExtra("text", text); + setResult(RESULT_OK, intent); + finish(); + } + } + } + }); + } + }); + + scannerView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mCodeScanner.startPreview(); + } + }); + } + + @Override + protected void onResume() { + super.onResume(); + mCodeScanner.startPreview(); + } + + @Override + protected void onPause() { + mCodeScanner.releaseResources(); + super.onPause(); + } + + public void onClose(View view) { + finish(); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + } +} diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SettingsActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SettingsActivity.java new file mode 100644 index 0000000..6a657f2 --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SettingsActivity.java @@ -0,0 +1,63 @@ +package org.easydarwin.easyplayer; + +import android.databinding.DataBindingUtil; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.CompoundButton; + +import org.easydarwin.easyplayer.databinding.ActivitySettingBinding; +import org.easydarwin.easyplayer.util.SPUtil; + +/** + * 设置页 + */ +public class SettingsActivity extends AppCompatActivity { + + private ActivitySettingBinding mBinding; + + @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 = DataBindingUtil.setContentView(this, R.layout.activity_setting); + + setSupportActionBar(mBinding.toolbar); + mBinding.toolbarBack.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + + mBinding.udpSwitch.setChecked(SPUtil.getUDPMode(this)); + mBinding.codecSwitch.setChecked(SPUtil.getMediaCodec(this)); + + mBinding.udpSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + SPUtil.setUDPMode(SettingsActivity.this, isChecked); + } + }); + + mBinding.codecSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + SPUtil.setMediaCodec(SettingsActivity.this, isChecked); + } + }); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + } +} diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SplashActivity.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SplashActivity.java new file mode 100644 index 0000000..94cdb7b --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/SplashActivity.java @@ -0,0 +1,144 @@ +package org.easydarwin.easyplayer; + +import android.annotation.SuppressLint; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.view.MotionEvent; +import android.view.View; +import android.widget.TextView; + +/** + * 启动页 + */ +public class SplashActivity extends AppCompatActivity { + /** + * Whether or not the system UI should be auto-hidden after + * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds. + */ + private static final boolean AUTO_HIDE = true; + + /** + * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after + * user interaction before hiding the system UI. + */ + private static final int AUTO_HIDE_DELAY_MILLIS = 3000; + + /** + * Some older devices needs a small delay between UI widget updates + * and a change of the status and navigation bar. + */ + private static final int UI_ANIMATION_DELAY = 300; + private final Handler mHideHandler = new Handler(); + private final Runnable mHidePart2Runnable = new Runnable() { + @SuppressLint("InlinedApi") + @Override + public void run() { + // Delayed removal of status and navigation bar + + // Note that some of these constants are new as of API 16 (Jelly Bean) + // and API 19 (KitKat). It is safe to use them, as they are inlined + // at compile-time and do nothing on earlier devices. + } + }; + private final Runnable mShowPart2Runnable = new Runnable() { + @Override + public void run() { + // Delayed display of UI elements + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.show(); + } + } + }; + private boolean mVisible; + private final Runnable mHideRunnable = new Runnable() { + @Override + public void run() { + hide(); + } + }; + /** + * Touch listener to use for in-layout UI controls to delay hiding the + * system UI. This is to prevent the jarring behavior of controls going away + * while interacting with activity UI. + */ + private final View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + if (AUTO_HIDE) { + delayedHide(AUTO_HIDE_DELAY_MILLIS); + } + return false; + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_splash); + + String versionName; + try { + versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; + } catch (PackageManager.NameNotFoundException e) { + versionName = "1.0"; + } + + TextView txtVersion = (TextView) findViewById(R.id.txt_version); + txtVersion.setText(String.format("EasyPlayer %s", versionName)); + + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + + // Trigger the initial hide() shortly after the activity has been + // created, to briefly hint to the user that UI controls + // are available. + delayedHide(0); + + mHideHandler.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 1500); + } + + private void hide() { + // Hide UI first + ActionBar actionBar = getSupportActionBar(); + + if (actionBar != null) { + actionBar.hide(); + } + + // Schedule a runnable to remove the status and navigation bar after a delay + mHideHandler.removeCallbacks(mShowPart2Runnable); + mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY); + } + + @SuppressLint("InlinedApi") + private void show() { + // Show the system bar + mVisible = true; + + // Schedule a runnable to display UI elements after a delay + mHideHandler.removeCallbacks(mHidePart2Runnable); + mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY); + } + + /** + * Schedules a call to hide() in [delay] milliseconds, canceling any + * previously scheduled calls. + */ + private void delayedHide(int delayMillis) { + mHideHandler.removeCallbacks(mHideRunnable); + mHideHandler.postDelayed(mHideRunnable, delayMillis); + } +} diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/TheApp.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/TheApp.java new file mode 100644 index 0000000..af18474 --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/TheApp.java @@ -0,0 +1,22 @@ +package org.easydarwin.easyplayer; + +import android.app.Application; +import android.database.sqlite.SQLiteDatabase; + +import org.easydarwin.easyplayer.data.EasyDBHelper; + +/** + * Created by afd on 8/13/16. + */ +public class TheApp extends Application { + + public static final String DEFAULT_SERVER_IP = "cloud.easydarwin.org"; + public static SQLiteDatabase sDB; + + @Override + public void onCreate() { + super.onCreate(); + + sDB = new EasyDBHelper(this).getWritableDatabase(); + } +} diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/data/EasyDBHelper.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/data/EasyDBHelper.java new file mode 100644 index 0000000..7b55b67 --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/data/EasyDBHelper.java @@ -0,0 +1,26 @@ +package org.easydarwin.easyplayer.data; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +/** + * Created by afd on 8/13/16. + */ +public class EasyDBHelper extends SQLiteOpenHelper { + + public static final String DB_NAME = "easydb.db"; + public EasyDBHelper(Context context) { + super(context, DB_NAME, null, 1); + } + + @Override + public void onCreate(SQLiteDatabase sqLiteDatabase) { + VideoSource.createTable(sqLiteDatabase); + } + + @Override + public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { + + } +} diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/data/VideoSource.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/data/VideoSource.java new file mode 100644 index 0000000..4a37260 --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/data/VideoSource.java @@ -0,0 +1,35 @@ +package org.easydarwin.easyplayer.data; + +import android.database.sqlite.SQLiteDatabase; +import android.provider.BaseColumns; + +/** + * Created by afd on 8/13/16. + */ +public class VideoSource implements BaseColumns { + public static final String URL = "url"; + public static final String TABLE_NAME = "video_source"; + /** + * -1 refers to manual added, otherwise pulled from server. + */ + public static final String INDEX = "_index"; + public static final String NAME = "name"; + public static final String AUDIENCE_NUMBER = "audience_number"; + + public static void createTable(SQLiteDatabase db) { + db.execSQL(String.format("CREATE TABLE IF NOT EXISTS %s (" + + "%s integer primary key autoincrement, " + + "%s integer default -1, " + + "%s VARCHAR(256) NOT NULL DEFAULT '', " + + "%s VARCHAR(256) NOT NULL DEFAULT '', " + + "%s integer DEFAULT 0 " + + ")", + TABLE_NAME, + _ID, + INDEX, + URL, + NAME, + AUDIENCE_NUMBER)); + } + +} diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/fragments/LocalFileFragment.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/fragments/LocalFileFragment.java new file mode 100644 index 0000000..2a0988c --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/fragments/LocalFileFragment.java @@ -0,0 +1,188 @@ +package org.easydarwin.easyplayer.fragments; + +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.Toast; + +import com.bumptech.glide.Glide; + +import org.easydarwin.easyplayer.PlayListActivity; +import org.easydarwin.easyplayer.ProVideoActivity; +import org.easydarwin.easyplayer.R; +import org.easydarwin.easyplayer.databinding.FragmentMediaFileBinding; +import org.easydarwin.easyplayer.databinding.ImagePickerItemBinding; +import org.easydarwin.easyplayer.util.FileUtil; + +import java.io.File; +import java.io.FilenameFilter; + +public class LocalFileFragment extends Fragment implements CompoundButton.OnCheckedChangeListener, View.OnClickListener { + public static final String KEY_IS_RECORD = "key_last_selection"; + + private boolean mShowMp4File; + private FragmentMediaFileBinding mBinding; + + SparseArray mImageChecked; + + private String mSuffix; + File mRoot = null; + File[] mSubFiles; + int mImgHeight; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(false); + + mImageChecked = new SparseArray<>(); + + mShowMp4File = getArguments().getBoolean(KEY_IS_RECORD); + mSuffix = mShowMp4File ? ".mp4" : ".jpg"; + + if (mShowMp4File) { + mRoot = new File(FileUtil.getMoviePath()); + } else { + mRoot = new File(FileUtil.getPicturePath()); + } + + File[] subFiles = mRoot.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String filename) { + return filename.endsWith(mSuffix); + } + }); + + if (subFiles == null) + subFiles = new File[0]; + + mSubFiles = subFiles; + mImgHeight = (int) (getResources().getDisplayMetrics().density * 100 + 0.5f); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_media_file, container, false); + return mBinding.getRoot(); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 3); + layoutManager.setOrientation(LinearLayoutManager.VERTICAL); + + mBinding.recycler.setLayoutManager(layoutManager); + + mBinding.recycler.setAdapter(new RecyclerView.Adapter() { + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + ImagePickerItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(getContext()), R.layout.image_picker_item, parent, false); + return new ImageItemHolder(binding); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { + ImageItemHolder holder = (ImageItemHolder) viewHolder; + holder.mCheckBox.setOnCheckedChangeListener(null); + holder.mCheckBox.setChecked(mImageChecked.get(position, false)); + holder.mCheckBox.setOnCheckedChangeListener(LocalFileFragment.this); + holder.mCheckBox.setTag(R.id.click_tag, holder); + holder.mImage.setTag(R.id.click_tag, holder); + + if (mShowMp4File) { + holder.mPlayImage.setVisibility(View.VISIBLE); + } else { + holder.mPlayImage.setVisibility(View.GONE); + } + + Glide.with(getContext()).load(mSubFiles[position]).into(holder.mImage); + } + + @Override + public int getItemCount() { + return mSubFiles.length; + } + }); + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { +// ImageItemHolder holder = (ImageItemHolder) buttonView.getTag(R.id.click_tag); +// int position = holder.getAdapterPosition(); + } + + @Override + public void onClick(View v) { + ImageItemHolder holder = (ImageItemHolder) v.getTag(R.id.click_tag); + if (holder.getAdapterPosition() == RecyclerView.NO_POSITION) { + return; + } + + final String path = mSubFiles[holder.getAdapterPosition()].getPath(); + if (TextUtils.isEmpty(path)) { + Toast.makeText(getContext(), "文件不存在", Toast.LENGTH_SHORT).show(); + return; + } + + File f = new File(path); + Uri uri = Uri.fromFile(f); + + if (path.endsWith(".jpg")) { + try { + Intent intent = new Intent(); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setAction(Intent.ACTION_VIEW); + intent.setDataAndType(uri, "image/*"); + startActivity(intent); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + } + } else if (path.endsWith(".mp4")) { + try { + Intent i = new Intent(getContext(), ProVideoActivity.class); + i.putExtra("videoPath", path); + startActivity(i); + +// Intent intent = new Intent(); +// intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); +// intent.setAction(Intent.ACTION_VIEW); +// intent.setDataAndType(uri, "video/*"); +// startActivity(intent); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + } + } + } + + class ImageItemHolder extends RecyclerView.ViewHolder { + public final CheckBox mCheckBox; + public final ImageView mImage; + public final ImageView mPlayImage; + + public ImageItemHolder(ImagePickerItemBinding binding) { + super(binding.getRoot()); + + mCheckBox = binding.imageCheckbox; + mImage = binding.imageIcon; + mPlayImage = binding.imagePlay; + mImage.setOnClickListener(LocalFileFragment.this); + } + } +} \ No newline at end of file diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/util/FileUtil.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/util/FileUtil.java new file mode 100644 index 0000000..77f519a --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/util/FileUtil.java @@ -0,0 +1,39 @@ +package org.easydarwin.easyplayer.util; + +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/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/util/SPUtil.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/util/SPUtil.java new file mode 100644 index 0000000..463906c --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/util/SPUtil.java @@ -0,0 +1,44 @@ +package org.easydarwin.easyplayer.util; + +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/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/ProVideoView.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/ProVideoView.java new file mode 100644 index 0000000..7cf2d8d --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/ProVideoView.java @@ -0,0 +1,222 @@ +package org.easydarwin.easyplayer.views; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.support.v4.app.ActivityCompat; +import android.text.TextUtils; +import android.util.AttributeSet; + +import org.easydarwin.easyplayer.ProVideoActivity; +import org.easydarwin.easyplayer.util.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; + +import static tv.danmaku.ijk.media.player.IjkMediaPlayer.native_active_days; + +/** + * 播放器 + * + * 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/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/SquareImageView.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/SquareImageView.java new file mode 100755 index 0000000..a30dbae --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/SquareImageView.java @@ -0,0 +1,33 @@ +package org.easydarwin.easyplayer.views; + +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + * Created by John on 2014/11/11. + */ +public class SquareImageView extends ImageView { + + public SquareImageView(Context context) { + super(context); + } + + public SquareImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SquareImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + setMeasuredDimension(widthMeasureSpec, widthMeasureSpec); + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } +} diff --git a/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/VideoControllerView.java b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/VideoControllerView.java new file mode 100644 index 0000000..1c41794 --- /dev/null +++ b/EasyPlayerPro/src/main/java/org/easydarwin/easyplayer/views/VideoControllerView.java @@ -0,0 +1,971 @@ +package org.easydarwin.easyplayer.views; + +import android.content.Context; +import android.graphics.Color; +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 org.easydarwin.easyplayer.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 View.OnClickListener mPauseListener = new View.OnClickListener() { + public void onClick(View v) { + doPauseResume(); + show(sDefaultTimeout); + } + }; + + // 全屏的点击事件 + private View.OnClickListener mFullscreenListener = new View.OnClickListener() { + public void onClick(View v) { + doToggleFullscreen(); + show(sDefaultTimeout); + } + }; + + // 录像的点击事件 + private View.OnClickListener mRecordingListener = new View.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 View.OnClickListener mRewindListener = new View.OnClickListener() { + public void onClick(View v) { + if (mPlayer == null) { + return; + } + + int pos = mPlayer.getCurrentPosition(); + pos -= 5000; // милисекунд + mPlayer.seekTo(pos); + setProgress(); + + show(sDefaultTimeout); + } + }; + + // 快进的点击事件 + private View.OnClickListener mFastListener = new View.OnClickListener() { + public void onClick(View v) { + if (mPlayer == null) { + return; + } + + int pos = mPlayer.getCurrentPosition(); + pos += 15000; // милисекунд + mPlayer.seekTo(pos); + setProgress(); + + show(sDefaultTimeout); + } + }; + + // 播放速度加快 + private View.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 View.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 View.OnClickListener takePicListener = new OnClickListener() { + @Override + public void onClick(View v) { + show(sDefaultTimeout); + if (mPlayer instanceof FullScreenAbleMediaPlayerControl) { + FullScreenAbleMediaPlayerControl player = (FullScreenAbleMediaPlayerControl) mPlayer; + player.takePicture(); + } + } + }; + + private View.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; + + FrameLayout.LayoutParams frameParams = new FrameLayout.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(); + + FrameLayout.LayoutParams tlp = new FrameLayout.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/EasyPlayerPro/src/main/jniLibs/arm64-v8a/libproffmpeg.so b/EasyPlayerPro/src/main/jniLibs/arm64-v8a/libproffmpeg.so new file mode 100755 index 0000000..48e7fcd Binary files /dev/null and b/EasyPlayerPro/src/main/jniLibs/arm64-v8a/libproffmpeg.so differ diff --git a/EasyPlayerPro/src/main/jniLibs/arm64-v8a/libproplayer.so b/EasyPlayerPro/src/main/jniLibs/arm64-v8a/libproplayer.so new file mode 100755 index 0000000..bca8d82 Binary files /dev/null and b/EasyPlayerPro/src/main/jniLibs/arm64-v8a/libproplayer.so differ diff --git a/EasyPlayerPro/src/main/jniLibs/arm64-v8a/libprosdl.so b/EasyPlayerPro/src/main/jniLibs/arm64-v8a/libprosdl.so new file mode 100755 index 0000000..82f09ef Binary files /dev/null and b/EasyPlayerPro/src/main/jniLibs/arm64-v8a/libprosdl.so differ diff --git a/EasyPlayerPro/src/main/jniLibs/armeabi-v7a/libproffmpeg.so b/EasyPlayerPro/src/main/jniLibs/armeabi-v7a/libproffmpeg.so new file mode 100755 index 0000000..1e6bbd3 Binary files /dev/null and b/EasyPlayerPro/src/main/jniLibs/armeabi-v7a/libproffmpeg.so differ diff --git a/EasyPlayerPro/src/main/jniLibs/armeabi-v7a/libproplayer.so b/EasyPlayerPro/src/main/jniLibs/armeabi-v7a/libproplayer.so new file mode 100755 index 0000000..1d38ba3 Binary files /dev/null and b/EasyPlayerPro/src/main/jniLibs/armeabi-v7a/libproplayer.so differ diff --git a/EasyPlayerPro/src/main/jniLibs/armeabi-v7a/libprosdl.so b/EasyPlayerPro/src/main/jniLibs/armeabi-v7a/libprosdl.so new file mode 100755 index 0000000..46a31e8 Binary files /dev/null and b/EasyPlayerPro/src/main/jniLibs/armeabi-v7a/libprosdl.so differ diff --git a/EasyPlayerPro/src/main/jniLibs/x86/libproffmpeg.so b/EasyPlayerPro/src/main/jniLibs/x86/libproffmpeg.so new file mode 100755 index 0000000..a412e27 Binary files /dev/null and b/EasyPlayerPro/src/main/jniLibs/x86/libproffmpeg.so differ diff --git a/EasyPlayerPro/src/main/jniLibs/x86/libproplayer.so b/EasyPlayerPro/src/main/jniLibs/x86/libproplayer.so new file mode 100755 index 0000000..d600055 Binary files /dev/null and b/EasyPlayerPro/src/main/jniLibs/x86/libproplayer.so differ diff --git a/EasyPlayerPro/src/main/jniLibs/x86/libprosdl.so b/EasyPlayerPro/src/main/jniLibs/x86/libprosdl.so new file mode 100755 index 0000000..3746423 Binary files /dev/null and b/EasyPlayerPro/src/main/jniLibs/x86/libprosdl.so differ diff --git a/EasyPlayerPro/src/main/res/anim/slide_bottom_in.xml b/EasyPlayerPro/src/main/res/anim/slide_bottom_in.xml new file mode 100644 index 0000000..ca60bf8 --- /dev/null +++ b/EasyPlayerPro/src/main/res/anim/slide_bottom_in.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/EasyPlayerPro/src/main/res/anim/slide_top_out.xml b/EasyPlayerPro/src/main/res/anim/slide_top_out.xml new file mode 100644 index 0000000..47406f2 --- /dev/null +++ b/EasyPlayerPro/src/main/res/anim/slide_top_out.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_address.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_address.png new file mode 100644 index 0000000..f266579 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_address.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_address_click.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_address_click.png new file mode 100644 index 0000000..834f60a Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_address_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_android_pro.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_android_pro.png new file mode 100644 index 0000000..c8e5645 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_android_pro.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_fast_click.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_fast_click.png new file mode 100644 index 0000000..31d6e01 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_fast_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_fast_white.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_fast_white.png new file mode 100644 index 0000000..0e45d24 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_fast_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_file.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_file.png new file mode 100644 index 0000000..8f1bb6d Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_file.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_file_click.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_file_click.png new file mode 100644 index 0000000..5e092e8 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_file_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_forward_click.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_forward_click.png new file mode 100644 index 0000000..bdd3b47 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_forward_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_forward_white.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_forward_white.png new file mode 100644 index 0000000..165c6d2 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_forward_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_full.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_full.png new file mode 100644 index 0000000..31d580c Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_full.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_full_white.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_full_white.png new file mode 100644 index 0000000..57bce24 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_full_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_lost.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_lost.png new file mode 100644 index 0000000..1a3984a Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_lost.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_moveback_click.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_moveback_click.png new file mode 100644 index 0000000..909bdf1 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_moveback_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_moveback_white.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_moveback_white.png new file mode 100644 index 0000000..61c481b Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_moveback_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_nav_back.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_nav_back.png new file mode 100644 index 0000000..945efe8 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_nav_back.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_play.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_play.png new file mode 100644 index 0000000..39e9270 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_play.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_play_white.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_play_white.png new file mode 100644 index 0000000..a688a73 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_play_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_player.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_player.png new file mode 100644 index 0000000..fae0bf3 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_player.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_scan.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_scan.png new file mode 100644 index 0000000..68ac14f Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_scan.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_scan_click.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_scan_click.png new file mode 100644 index 0000000..fd21e68 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_scan_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_set.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_set.png new file mode 100644 index 0000000..e738f40 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_set.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_set_click.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_set_click.png new file mode 100644 index 0000000..94a499d Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_set_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_slow_click.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_slow_click.png new file mode 100644 index 0000000..a42ea3f Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_slow_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_slow_white.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_slow_white.png new file mode 100644 index 0000000..18c8de4 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_slow_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_snapshot_click.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_snapshot_click.png new file mode 100644 index 0000000..4534774 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_snapshot_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_snapshot_white.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_snapshot_white.png new file mode 100644 index 0000000..6efc5ca Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_snapshot_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_splash_bg.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_splash_bg.png new file mode 100644 index 0000000..4141901 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_splash_bg.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_stop.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_stop.png new file mode 100644 index 0000000..7b546c3 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_stop.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_stop_white.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_stop_white.png new file mode 100644 index 0000000..3c25607 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_stop_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_stretch_click.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_stretch_click.png new file mode 100644 index 0000000..adc06e5 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_stretch_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_stretch_white.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_stretch_white.png new file mode 100644 index 0000000..bfa52a1 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_stretch_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_thumb.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_thumb.png new file mode 100644 index 0000000..b8b252b Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_thumb.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_top_bg.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_top_bg.png new file mode 100644 index 0000000..850eea3 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_top_bg.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_version1.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_version1.png new file mode 100644 index 0000000..b28b0b8 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_version1.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_version2.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_version2.png new file mode 100644 index 0000000..12a59a3 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_version2.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_version3.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_version3.png new file mode 100644 index 0000000..0cffadb Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_version3.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_videotape_click.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_videotape_click.png new file mode 100644 index 0000000..ce8875c Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_videotape_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/new_videotape_white.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_videotape_white.png new file mode 100644 index 0000000..144460f Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/new_videotape_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xhdpi/placeholder.png b/EasyPlayerPro/src/main/res/drawable-xhdpi/placeholder.png new file mode 100644 index 0000000..f42b265 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xhdpi/placeholder.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_address.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_address.png new file mode 100644 index 0000000..677c583 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_address.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_address_click.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_address_click.png new file mode 100644 index 0000000..3bb0ac6 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_address_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_fast_click.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_fast_click.png new file mode 100644 index 0000000..951c599 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_fast_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_fast_white.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_fast_white.png new file mode 100644 index 0000000..152cfd4 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_fast_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_file.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_file.png new file mode 100644 index 0000000..d1201cf Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_file.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_file_click.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_file_click.png new file mode 100644 index 0000000..54c4262 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_file_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_forward_click.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_forward_click.png new file mode 100644 index 0000000..e4db528 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_forward_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_forward_white.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_forward_white.png new file mode 100644 index 0000000..8b9747a Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_forward_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_full.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_full.png new file mode 100644 index 0000000..037c43d Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_full.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_full_white.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_full_white.png new file mode 100644 index 0000000..f6ca476 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_full_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_lost.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_lost.png new file mode 100644 index 0000000..6cad4ce Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_lost.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_moveback_click.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_moveback_click.png new file mode 100644 index 0000000..4cbfbb1 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_moveback_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_moveback_white.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_moveback_white.png new file mode 100644 index 0000000..eee6acb Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_moveback_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_nav_back.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_nav_back.png new file mode 100644 index 0000000..beefba7 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_nav_back.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_play.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_play.png new file mode 100644 index 0000000..8037be2 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_play.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_play_white.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_play_white.png new file mode 100644 index 0000000..e7f9db0 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_play_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_player.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_player.png new file mode 100644 index 0000000..00beb39 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_player.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_scan.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_scan.png new file mode 100644 index 0000000..e665c87 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_scan.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_scan_click.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_scan_click.png new file mode 100644 index 0000000..5f8f20d Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_scan_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_set.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_set.png new file mode 100644 index 0000000..9a6ef75 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_set.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_set_click.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_set_click.png new file mode 100644 index 0000000..ab751d1 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_set_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_slow_click.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_slow_click.png new file mode 100644 index 0000000..d5213e2 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_slow_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_slow_white.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_slow_white.png new file mode 100644 index 0000000..74d6790 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_slow_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_snapshot_click.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_snapshot_click.png new file mode 100644 index 0000000..96453d1 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_snapshot_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_snapshot_white.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_snapshot_white.png new file mode 100644 index 0000000..15bc656 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_snapshot_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_splash_bg.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_splash_bg.png new file mode 100644 index 0000000..a5c32af Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_splash_bg.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_splash_image.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_splash_image.png new file mode 100644 index 0000000..206c559 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_splash_image.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_stop.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_stop.png new file mode 100644 index 0000000..033452d Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_stop.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_stop_white.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_stop_white.png new file mode 100644 index 0000000..ab97567 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_stop_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_stretch_click.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_stretch_click.png new file mode 100644 index 0000000..25795a4 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_stretch_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_stretch_white.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_stretch_white.png new file mode 100644 index 0000000..dba0f4d Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_stretch_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_top_bg.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_top_bg.png new file mode 100644 index 0000000..423bded Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_top_bg.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_version1.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_version1.png new file mode 100644 index 0000000..bbf9f08 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_version1.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_version2.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_version2.png new file mode 100644 index 0000000..1e71ba8 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_version2.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_version3.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_version3.png new file mode 100644 index 0000000..d1d99fc Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_version3.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_videotape_click.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_videotape_click.png new file mode 100644 index 0000000..564b213 Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_videotape_click.png differ diff --git a/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_videotape_white.png b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_videotape_white.png new file mode 100644 index 0000000..7c60f0f Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable-xxhdpi/new_videotape_white.png differ diff --git a/EasyPlayerPro/src/main/res/drawable/anim.xml b/EasyPlayerPro/src/main/res/drawable/anim.xml new file mode 100755 index 0000000..436c48c --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/anim.xml @@ -0,0 +1,7 @@ + + diff --git a/EasyPlayerPro/src/main/res/drawable/loading.gif b/EasyPlayerPro/src/main/res/drawable/loading.gif new file mode 100644 index 0000000..06e31fd Binary files /dev/null and b/EasyPlayerPro/src/main/res/drawable/loading.gif differ diff --git a/EasyPlayerPro/src/main/res/drawable/new_address_btn.xml b/EasyPlayerPro/src/main/res/drawable/new_address_btn.xml new file mode 100755 index 0000000..601989a --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/new_address_btn.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/drawable/new_color_text.xml b/EasyPlayerPro/src/main/res/drawable/new_color_text.xml new file mode 100755 index 0000000..32e00f7 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/new_color_text.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/EasyPlayerPro/src/main/res/drawable/new_fast_btn.xml b/EasyPlayerPro/src/main/res/drawable/new_fast_btn.xml new file mode 100755 index 0000000..2930484 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/new_fast_btn.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/drawable/new_file_btn.xml b/EasyPlayerPro/src/main/res/drawable/new_file_btn.xml new file mode 100755 index 0000000..f44e8c6 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/new_file_btn.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/drawable/new_forward_btn.xml b/EasyPlayerPro/src/main/res/drawable/new_forward_btn.xml new file mode 100755 index 0000000..5936eac --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/new_forward_btn.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/drawable/new_moveback_btn.xml b/EasyPlayerPro/src/main/res/drawable/new_moveback_btn.xml new file mode 100755 index 0000000..a0e4a09 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/new_moveback_btn.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/drawable/new_scan_btn.xml b/EasyPlayerPro/src/main/res/drawable/new_scan_btn.xml new file mode 100755 index 0000000..b3720d4 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/new_scan_btn.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/drawable/new_setting_btn.xml b/EasyPlayerPro/src/main/res/drawable/new_setting_btn.xml new file mode 100755 index 0000000..79fe5f0 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/new_setting_btn.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/drawable/new_slow_btn.xml b/EasyPlayerPro/src/main/res/drawable/new_slow_btn.xml new file mode 100755 index 0000000..fec94ab --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/new_slow_btn.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/drawable/new_snapshot_btn.xml b/EasyPlayerPro/src/main/res/drawable/new_snapshot_btn.xml new file mode 100755 index 0000000..310785f --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/new_snapshot_btn.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/drawable/new_stretch_btn.xml b/EasyPlayerPro/src/main/res/drawable/new_stretch_btn.xml new file mode 100755 index 0000000..816dda3 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/new_stretch_btn.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/drawable/new_videotape_btn.xml b/EasyPlayerPro/src/main/res/drawable/new_videotape_btn.xml new file mode 100755 index 0000000..67ca41b --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/new_videotape_btn.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/drawable/red_dot.xml b/EasyPlayerPro/src/main/res/drawable/red_dot.xml new file mode 100644 index 0000000..97bad9f --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/red_dot.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/drawable/seekbar_progress_drawable.xml b/EasyPlayerPro/src/main/res/drawable/seekbar_progress_drawable.xml new file mode 100755 index 0000000..6804e93 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/seekbar_progress_drawable.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/EasyPlayerPro/src/main/res/drawable/switch_custom_thumb_off.xml b/EasyPlayerPro/src/main/res/drawable/switch_custom_thumb_off.xml new file mode 100755 index 0000000..8886e54 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/switch_custom_thumb_off.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/EasyPlayerPro/src/main/res/drawable/switch_custom_thumb_on.xml b/EasyPlayerPro/src/main/res/drawable/switch_custom_thumb_on.xml new file mode 100755 index 0000000..8886e54 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/switch_custom_thumb_on.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/EasyPlayerPro/src/main/res/drawable/switch_custom_thumb_selector.xml b/EasyPlayerPro/src/main/res/drawable/switch_custom_thumb_selector.xml new file mode 100755 index 0000000..40656db --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/switch_custom_thumb_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/EasyPlayerPro/src/main/res/drawable/switch_custom_track_off.xml b/EasyPlayerPro/src/main/res/drawable/switch_custom_track_off.xml new file mode 100755 index 0000000..ccaa691 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/switch_custom_track_off.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/EasyPlayerPro/src/main/res/drawable/switch_custom_track_on.xml b/EasyPlayerPro/src/main/res/drawable/switch_custom_track_on.xml new file mode 100755 index 0000000..700c376 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/switch_custom_track_on.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/EasyPlayerPro/src/main/res/drawable/switch_custom_track_selector.xml b/EasyPlayerPro/src/main/res/drawable/switch_custom_track_selector.xml new file mode 100755 index 0000000..c93a0df --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/switch_custom_track_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/EasyPlayerPro/src/main/res/drawable/transparent_dot.xml b/EasyPlayerPro/src/main/res/drawable/transparent_dot.xml new file mode 100644 index 0000000..9db24c6 --- /dev/null +++ b/EasyPlayerPro/src/main/res/drawable/transparent_dot.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/activity_about.xml b/EasyPlayerPro/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..ae76471 --- /dev/null +++ b/EasyPlayerPro/src/main/res/layout/activity_about.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/activity_main_pro.xml b/EasyPlayerPro/src/main/res/layout/activity_main_pro.xml new file mode 100644 index 0000000..e34b62b --- /dev/null +++ b/EasyPlayerPro/src/main/res/layout/activity_main_pro.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/EasyPlayerPro/src/main/res/layout/activity_media_files.xml b/EasyPlayerPro/src/main/res/layout/activity_media_files.xml new file mode 100755 index 0000000..bf33680 --- /dev/null +++ b/EasyPlayerPro/src/main/res/layout/activity_media_files.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/EasyPlayerPro/src/main/res/layout/activity_play_list.xml b/EasyPlayerPro/src/main/res/layout/activity_play_list.xml new file mode 100644 index 0000000..43b9e7b --- /dev/null +++ b/EasyPlayerPro/src/main/res/layout/activity_play_list.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/activity_scan_qr.xml b/EasyPlayerPro/src/main/res/layout/activity_scan_qr.xml new file mode 100644 index 0000000..298d711 --- /dev/null +++ b/EasyPlayerPro/src/main/res/layout/activity_scan_qr.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/activity_setting.xml b/EasyPlayerPro/src/main/res/layout/activity_setting.xml new file mode 100644 index 0000000..ce05fa7 --- /dev/null +++ b/EasyPlayerPro/src/main/res/layout/activity_setting.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/activity_splash.xml b/EasyPlayerPro/src/main/res/layout/activity_splash.xml new file mode 100644 index 0000000..2c62023 --- /dev/null +++ b/EasyPlayerPro/src/main/res/layout/activity_splash.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/fragment_media_file.xml b/EasyPlayerPro/src/main/res/layout/fragment_media_file.xml new file mode 100755 index 0000000..c5e856d --- /dev/null +++ b/EasyPlayerPro/src/main/res/layout/fragment_media_file.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/image_picker_item.xml b/EasyPlayerPro/src/main/res/layout/image_picker_item.xml new file mode 100755 index 0000000..5f89470 --- /dev/null +++ b/EasyPlayerPro/src/main/res/layout/image_picker_item.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/media_controller.xml b/EasyPlayerPro/src/main/res/layout/media_controller.xml new file mode 100644 index 0000000..16211b1 --- /dev/null +++ b/EasyPlayerPro/src/main/res/layout/media_controller.xml @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/new_media_source_dialog.xml b/EasyPlayerPro/src/main/res/layout/new_media_source_dialog.xml new file mode 100644 index 0000000..c99db64 --- /dev/null +++ b/EasyPlayerPro/src/main/res/layout/new_media_source_dialog.xml @@ -0,0 +1,35 @@ + + + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/layout/video_source_item.xml b/EasyPlayerPro/src/main/res/layout/video_source_item.xml new file mode 100644 index 0000000..0a4edf3 --- /dev/null +++ b/EasyPlayerPro/src/main/res/layout/video_source_item.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/EasyPlayerPro/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..433c2f5 Binary files /dev/null and b/EasyPlayerPro/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/EasyPlayerPro/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/EasyPlayerPro/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..86007c6 Binary files /dev/null and b/EasyPlayerPro/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/EasyPlayerPro/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/EasyPlayerPro/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..1fe344a Binary files /dev/null and b/EasyPlayerPro/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/EasyPlayerPro/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/EasyPlayerPro/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3f2a48a Binary files /dev/null and b/EasyPlayerPro/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/EasyPlayerPro/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/EasyPlayerPro/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..4fb5705 Binary files /dev/null and b/EasyPlayerPro/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/EasyPlayerPro/src/main/res/values-sw360dp/dimens.xml b/EasyPlayerPro/src/main/res/values-sw360dp/dimens.xml new file mode 100644 index 0000000..a6dd140 --- /dev/null +++ b/EasyPlayerPro/src/main/res/values-sw360dp/dimens.xml @@ -0,0 +1,7 @@ + + + + 16dp + 16dp + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/values-v21/styles.xml b/EasyPlayerPro/src/main/res/values-v21/styles.xml new file mode 100644 index 0000000..f11f745 --- /dev/null +++ b/EasyPlayerPro/src/main/res/values-v21/styles.xml @@ -0,0 +1,3 @@ + + + diff --git a/EasyPlayerPro/src/main/res/values/attrs.xml b/EasyPlayerPro/src/main/res/values/attrs.xml new file mode 100644 index 0000000..7ce840e --- /dev/null +++ b/EasyPlayerPro/src/main/res/values/attrs.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/EasyPlayerPro/src/main/res/values/colors.xml b/EasyPlayerPro/src/main/res/values/colors.xml new file mode 100644 index 0000000..510727f --- /dev/null +++ b/EasyPlayerPro/src/main/res/values/colors.xml @@ -0,0 +1,21 @@ + + + #ffffff + #0dbb92 + #ff4081 + + #66000000 + + #1ED5CA + #03D1E3 + + #FFFFFF + + #03c6e3 + #a6a6a6 + + #2cff1c + #eee604 + #f64a4a + #ebebeb + diff --git a/EasyPlayerPro/src/main/res/values/dimens.xml b/EasyPlayerPro/src/main/res/values/dimens.xml new file mode 100644 index 0000000..02d4637 --- /dev/null +++ b/EasyPlayerPro/src/main/res/values/dimens.xml @@ -0,0 +1,6 @@ + + + 16dp + 16dp + + diff --git a/EasyPlayerPro/src/main/res/values/id.xml b/EasyPlayerPro/src/main/res/values/id.xml new file mode 100644 index 0000000..0d2c4cc --- /dev/null +++ b/EasyPlayerPro/src/main/res/values/id.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/values/ids.xml b/EasyPlayerPro/src/main/res/values/ids.xml new file mode 100644 index 0000000..f65ae81 --- /dev/null +++ b/EasyPlayerPro/src/main/res/values/ids.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/values/string_pro.xml b/EasyPlayerPro/src/main/res/values/string_pro.xml new file mode 100644 index 0000000..a6f3dee --- /dev/null +++ b/EasyPlayerPro/src/main/res/values/string_pro.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/EasyPlayerPro/src/main/res/values/strings.xml b/EasyPlayerPro/src/main/res/values/strings.xml new file mode 100644 index 0000000..7490cea --- /dev/null +++ b/EasyPlayerPro/src/main/res/values/strings.xml @@ -0,0 +1,22 @@ + + EasyPlayer + basic + + Settings + + + + + + + + + + Splash + + + + desc + + EasyPlayerPro是由 TSINGSEE青犀开放平台 开发和维护的一款精炼、易用、高效、稳定的流媒体播放器,支持RTSP(RTP over TCP/UDP)、RTMP、HTTP、HLS、TCP、UDP等多种流媒体协议,支持各种各样编码格式的流媒体音视频直播流、点播流、文件播放! + diff --git a/EasyPlayerPro/src/main/res/values/strings_pref.xml b/EasyPlayerPro/src/main/res/values/strings_pref.xml new file mode 100644 index 0000000..2e85ecb --- /dev/null +++ b/EasyPlayerPro/src/main/res/values/strings_pref.xml @@ -0,0 +1,39 @@ + + + + + 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 + + + + + diff --git a/EasyPlayerPro/src/main/res/values/styles.xml b/EasyPlayerPro/src/main/res/values/styles.xml new file mode 100644 index 0000000..a5eff47 --- /dev/null +++ b/EasyPlayerPro/src/main/res/values/styles.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + diff --git a/EasyPlayerPro/src/main/res/values/update_strings.xml b/EasyPlayerPro/src/main/res/values/update_strings.xml new file mode 100644 index 0000000..1ee237b --- /dev/null +++ b/EasyPlayerPro/src/main/res/values/update_strings.xml @@ -0,0 +1,4 @@ + + + org.easydarwin.easyplayer.pro.fileprovider + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c72512 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# EasyPlayerPro # + +EasyPlayerPro是由[TSINGSEE青犀开放平台](http://open.tsingsee.com "TSINGSEE青犀开放平台")开发和维护的一款精炼、易用、高效、稳定的流媒体播放器,支持RTSP(RTP over TCP/UDP)、RTMP、HTTP、HLS、TCP、UDP等多种流媒体协议,支持各种各样编码格式的流媒体音视频直播流、点播流、文件播放! + +An elegant, simple, fast android RTSP/RTMP/HLS/HTTP Player.EasyPlayer support RTSP(RTP over TCP/UDP),RTMP,HTTP,HLS,cover all kinds of streaming media! + +# 版本下载 # + +- EasyPlayerPro-Android +[http://app.tsingsee.com/EasyPlayerPro](http://app.tsingsee.com/EasyPlayerPro) + +![](http://www.easydarwin.org/github/images/app/2020/easy_player_pro_android.png) + +- EasyPlayerPro-iOS + +![](http://www.easydarwin.org/github/images/app/2020/easy_player_pro_iOS.png) + +## 技术支持 ## + +- 邮件:[support@tsingsee.com](mailto:support@tsingsee.com) + +- QQ交流群:**544917793** + + +## 获取更多信息 ## + +TSINGSEE青犀开放平台:[http://open.tsingsee.com](http://open.tsingsee.com "TSINGSEE青犀开放平台") + +Copyright © TSINGSEE.com 2012~2021 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..a56062d --- /dev/null +++ b/build.gradle @@ -0,0 +1,22 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.4.1' + } +} + +allprojects { + repositories { + jcenter() + maven { url "https://jitpack.io" } + google() + } +} + +ext{ + support_version='26.1.0' +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6b319b5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Apr 06 13:56:34 CST 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega 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/androidTest/java/tv/danmaku/ijk/media/player/ApplicationTest.java b/ijkplayer-java/src/androidTest/java/tv/danmaku/ijk/media/player/ApplicationTest.java new file mode 100644 index 0000000..60d1d73 --- /dev/null +++ b/ijkplayer-java/src/androidTest/java/tv/danmaku/ijk/media/player/ApplicationTest.java @@ -0,0 +1,13 @@ +package tv.danmaku.ijk.media.player; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file 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..c054b15 --- /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 = ""; + + // 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/import-summary.txt b/import-summary.txt new file mode 100644 index 0000000..f83e3ca --- /dev/null +++ b/import-summary.txt @@ -0,0 +1,203 @@ +ECLIPSE ANDROID PROJECT IMPORT SUMMARY +====================================== + +Manifest Merging: +----------------- +Your project uses libraries that provide manifests, and your Eclipse +project did not explicitly turn on manifest merging. In Android Gradle +projects, manifests are always merged (meaning that contents from your +libraries' manifests will be merged into the app manifest. If you had +manually copied contents from library manifests into your app manifest +you may need to remove these for the app to build correctly. + +Ignored Files: +-------------- +The following files were *not* copied into the new Gradle project; you +should evaluate whether these are still needed in your project and if +so manually move them: + +From MediaUploader: +* .idea\ +* .idea\.name +* .idea\MediaUploader.iml +* .idea\compiler.xml +* .idea\copyright\ +* .idea\copyright\profiles_settings.xml +* .idea\encodings.xml +* .idea\misc.xml +* .idea\modules.xml +* .idea\vcs.xml +* .idea\workspace.xml +* ic_launcher-web.png +* proguard-project.txt +From libstreaming: +* .gitignore +* LICENSE +* README.md +* build.xml +* doc\ +* doc\allclasses-frame.html +* doc\allclasses-noframe.html +* doc\constant-values.html +* doc\deprecated-list.html +* doc\help-doc.html +* doc\index-all.html +* doc\index.html +* doc\net\ +* doc\net\majorkernelpanic\ +* doc\net\majorkernelpanic\streaming\ +* doc\net\majorkernelpanic\streaming\MediaStream.html +* doc\net\majorkernelpanic\streaming\Session.Callback.html +* doc\net\majorkernelpanic\streaming\Session.html +* doc\net\majorkernelpanic\streaming\SessionBuilder.html +* doc\net\majorkernelpanic\streaming\Stream.html +* doc\net\majorkernelpanic\streaming\audio\ +* doc\net\majorkernelpanic\streaming\audio\AACStream.html +* doc\net\majorkernelpanic\streaming\audio\AMRNBStream.html +* doc\net\majorkernelpanic\streaming\audio\AudioQuality.html +* doc\net\majorkernelpanic\streaming\audio\AudioStream.html +* doc\net\majorkernelpanic\streaming\audio\package-frame.html +* doc\net\majorkernelpanic\streaming\audio\package-summary.html +* doc\net\majorkernelpanic\streaming\audio\package-tree.html +* doc\net\majorkernelpanic\streaming\exceptions\ +* doc\net\majorkernelpanic\streaming\exceptions\CameraInUseException.html +* doc\net\majorkernelpanic\streaming\exceptions\ConfNotSupportedException.html +* doc\net\majorkernelpanic\streaming\exceptions\InvalidSurfaceException.html +* doc\net\majorkernelpanic\streaming\exceptions\StorageUnavailableException.html +* doc\net\majorkernelpanic\streaming\exceptions\package-frame.html +* doc\net\majorkernelpanic\streaming\exceptions\package-summary.html +* doc\net\majorkernelpanic\streaming\exceptions\package-tree.html +* doc\net\majorkernelpanic\streaming\gl\ +* doc\net\majorkernelpanic\streaming\gl\SurfaceManager.html +* doc\net\majorkernelpanic\streaming\gl\SurfaceView.ViewAspectRatioMeasurer.html +* doc\net\majorkernelpanic\streaming\gl\SurfaceView.html +* doc\net\majorkernelpanic\streaming\gl\TextureManager.html +* doc\net\majorkernelpanic\streaming\gl\package-frame.html +* doc\net\majorkernelpanic\streaming\gl\package-summary.html +* doc\net\majorkernelpanic\streaming\gl\package-tree.html +* doc\net\majorkernelpanic\streaming\hw\ +* doc\net\majorkernelpanic\streaming\hw\CodecManager.html +* doc\net\majorkernelpanic\streaming\hw\EncoderDebugger.html +* doc\net\majorkernelpanic\streaming\hw\NV21Convertor.html +* doc\net\majorkernelpanic\streaming\hw\package-frame.html +* doc\net\majorkernelpanic\streaming\hw\package-summary.html +* doc\net\majorkernelpanic\streaming\hw\package-tree.html +* doc\net\majorkernelpanic\streaming\mp4\ +* doc\net\majorkernelpanic\streaming\mp4\MP4Config.html +* doc\net\majorkernelpanic\streaming\mp4\MP4Parser.html +* doc\net\majorkernelpanic\streaming\mp4\package-frame.html +* doc\net\majorkernelpanic\streaming\mp4\package-summary.html +* doc\net\majorkernelpanic\streaming\mp4\package-tree.html +* doc\net\majorkernelpanic\streaming\package-frame.html +* doc\net\majorkernelpanic\streaming\package-summary.html +* doc\net\majorkernelpanic\streaming\package-tree.html +* doc\net\majorkernelpanic\streaming\rtcp\ +* doc\net\majorkernelpanic\streaming\rtcp\SenderReport.html +* doc\net\majorkernelpanic\streaming\rtcp\package-frame.html +* doc\net\majorkernelpanic\streaming\rtcp\package-summary.html +* doc\net\majorkernelpanic\streaming\rtcp\package-tree.html +* doc\net\majorkernelpanic\streaming\rtp\ +* doc\net\majorkernelpanic\streaming\rtp\AACADTSPacketizer.html +* doc\net\majorkernelpanic\streaming\rtp\AACLATMPacketizer.html +* doc\net\majorkernelpanic\streaming\rtp\AMRNBPacketizer.html +* doc\net\majorkernelpanic\streaming\rtp\AbstractPacketizer.html +* doc\net\majorkernelpanic\streaming\rtp\H263Packetizer.html +* doc\net\majorkernelpanic\streaming\rtp\H264Packetizer.html +* doc\net\majorkernelpanic\streaming\rtp\MediaCodecInputStream.html +* doc\net\majorkernelpanic\streaming\rtp\RtpSocket.html +* doc\net\majorkernelpanic\streaming\rtp\package-frame.html +* doc\net\majorkernelpanic\streaming\rtp\package-summary.html +* doc\net\majorkernelpanic\streaming\rtp\package-tree.html +* doc\net\majorkernelpanic\streaming\rtsp\ +* doc\net\majorkernelpanic\streaming\rtsp\RtspClient.Callback.html +* doc\net\majorkernelpanic\streaming\rtsp\RtspClient.html +* doc\net\majorkernelpanic\streaming\rtsp\RtspServer.CallbackListener.html +* doc\net\majorkernelpanic\streaming\rtsp\RtspServer.LocalBinder.html +* doc\net\majorkernelpanic\streaming\rtsp\RtspServer.html +* doc\net\majorkernelpanic\streaming\rtsp\UriParser.html +* doc\net\majorkernelpanic\streaming\rtsp\package-frame.html +* doc\net\majorkernelpanic\streaming\rtsp\package-summary.html +* doc\net\majorkernelpanic\streaming\rtsp\package-tree.html +* doc\net\majorkernelpanic\streaming\video\ +* doc\net\majorkernelpanic\streaming\video\CodecManager.html +* doc\net\majorkernelpanic\streaming\video\H263Stream.html +* doc\net\majorkernelpanic\streaming\video\H264Stream.html +* doc\net\majorkernelpanic\streaming\video\VideoQuality.html +* doc\net\majorkernelpanic\streaming\video\VideoStream.html +* doc\net\majorkernelpanic\streaming\video\package-frame.html +* doc\net\majorkernelpanic\streaming\video\package-summary.html +* doc\net\majorkernelpanic\streaming\video\package-tree.html +* doc\overview-frame.html +* doc\overview-summary.html +* doc\overview-tree.html +* doc\package-list +* doc\resources\ +* doc\resources\background.gif +* doc\resources\tab.gif +* doc\resources\titlebar.gif +* doc\resources\titlebar_end.gif +* doc\serialized-form.html +* doc\stylesheet.css +* pom.xml +* proguard-project.txt + +Replaced Jars with Dependencies: +-------------------------------- +The importer recognized the following .jar files as third party +libraries and replaced them with Gradle dependencies instead. This has +the advantage that more explicit version information is known, and the +libraries can be updated automatically. However, it is possible that +the .jar file in your project was of an older version than the +dependency we picked, which could render the project not compileable. +You can disable the jar replacement in the import wizard and try again: + +android-support-v4.jar => com.android.support:support-v4:19.1.0 +android-support-v7-appcompat.jar => com.android.support:appcompat-v7:19.1.0 +gson-2.2.2_android.jar => com.google.code.gson:gson:2.3.1 + +Replaced Libraries with Dependencies: +------------------------------------- +The importer recognized the following library projects as third party +libraries and replaced them with Gradle dependencies instead. This has +the advantage that more explicit version information is known, and the +libraries can be updated automatically. However, it is possible that +the source files in your project were of an older version than the +dependency we picked, which could render the project not compileable. +You can disable the library replacement in the import wizard and try +again: + +android-support-v7-appcompat => [com.android.support:appcompat-v7:19.1.0] + +Moved Files: +------------ +Android Gradle projects use a different directory structure than ADT +Eclipse projects. Here's how the projects were restructured: + +In libstreaming: +* AndroidManifest.xml => libstreaming\src\main\AndroidManifest.xml +* assets\ => libstreaming\src\main\assets +* libs\android-logging-log4j-1.0.3.jar => libstreaming\libs\android-logging-log4j-1.0.3.jar +* libs\log4j-1.2.17.jar => libstreaming\libs\log4j-1.2.17.jar +* res\ => libstreaming\src\main\res +* src\ => libstreaming\src\main\java\ +In MediaUploader: +* AndroidManifest.xml => mediaUploader\src\main\AndroidManifest.xml +* assets\ => mediaUploader\src\main\assets +* libs\armeabi\libUtils.so => mediaUploader\src\main\jniLibs\armeabi\libUtils.so +* res\ => mediaUploader\src\main\res\ +* src\ => mediaUploader\src\main\java\ + +Next Steps: +----------- +You can now build the project. The Gradle project needs network +connectivity to download dependencies. + +Bugs: +----- +If for some reason your project does not build, and you determine that +it is due to a bug or limitation of the Eclipse to Gradle importer, +please file a bug at http://b.android.com with category +Component-Tools. + +(This import summary is for your information only, and can be deleted +after import once you are satisfied with the results.) diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..c5a1f8a --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +include ':EasyPlayerPro' +include ':ijkplayer-java'