init project
1
EasyPlayerPro/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
97
EasyPlayerPro/build.gradle
Normal file
@@ -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')
|
||||
}
|
||||
BIN
EasyPlayerPro/libs/gson-2.1.jar
Normal file
30
EasyPlayerPro/proguard-rules.pro
vendored
Normal file
@@ -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{
|
||||
*;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.esaydarwin.rtsp.player;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.)
|
||||
* <p>
|
||||
* The output file will be something like "/sdcard/test.640x480.mp4".
|
||||
* <p>
|
||||
* (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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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".
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
EasyPlayerPro/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.easydarwin.easyplayer">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_LOGS" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
|
||||
|
||||
<application
|
||||
android:name=".TheApp"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher_foreground"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<meta-data
|
||||
android:name="UMENG_APPKEY"
|
||||
android:value="5700bb7467e58ef3fd000648" />
|
||||
<meta-data
|
||||
android:name="UMENG_CHANNEL"
|
||||
android:value="@string/app_flavor" />
|
||||
|
||||
<activity
|
||||
android:name=".PlayListActivity"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".SplashActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:label="@string/title_activity_splash"
|
||||
android:theme="@style/FullscreenTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".ProVideoActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:theme="@style/FullscreenTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".AboutActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:label="@string/title_activity_settings" />
|
||||
|
||||
<activity android:name=".MediaFilesActivity" android:label="文件夹"/>
|
||||
<activity android:name=".ScanQRActivity" android:label="扫码"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
BIN
EasyPlayerPro/src/main/ic_launcher-web.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
package org.easydarwin.easyplayer;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Boolean> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<VideoControllerView> mView;
|
||||
|
||||
MessageHandler(VideoControllerView view) {
|
||||
mView = new WeakReference<VideoControllerView>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
EasyPlayerPro/src/main/jniLibs/arm64-v8a/libproffmpeg.so
Executable file
BIN
EasyPlayerPro/src/main/jniLibs/arm64-v8a/libproplayer.so
Executable file
BIN
EasyPlayerPro/src/main/jniLibs/arm64-v8a/libprosdl.so
Executable file
BIN
EasyPlayerPro/src/main/jniLibs/armeabi-v7a/libproffmpeg.so
Executable file
BIN
EasyPlayerPro/src/main/jniLibs/armeabi-v7a/libproplayer.so
Executable file
BIN
EasyPlayerPro/src/main/jniLibs/armeabi-v7a/libprosdl.so
Executable file
BIN
EasyPlayerPro/src/main/jniLibs/x86/libproffmpeg.so
Executable file
BIN
EasyPlayerPro/src/main/jniLibs/x86/libproplayer.so
Executable file
BIN
EasyPlayerPro/src/main/jniLibs/x86/libprosdl.so
Executable file
9
EasyPlayerPro/src/main/res/anim/slide_bottom_in.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<translate
|
||||
android:duration="300"
|
||||
android:fromYDelta="100.0%p"
|
||||
android:toYDelta="0" />
|
||||
|
||||
</set>
|
||||
9
EasyPlayerPro/src/main/res/anim/slide_top_out.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<translate
|
||||
android:duration="300"
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="100.0%p" />
|
||||
|
||||
</set>
|
||||
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_address.png
Normal file
|
After Width: | Height: | Size: 975 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_address_click.png
Normal file
|
After Width: | Height: | Size: 948 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_android_pro.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_fast_click.png
Normal file
|
After Width: | Height: | Size: 324 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_fast_white.png
Normal file
|
After Width: | Height: | Size: 302 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_file.png
Normal file
|
After Width: | Height: | Size: 937 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_file_click.png
Normal file
|
After Width: | Height: | Size: 939 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_forward_click.png
Normal file
|
After Width: | Height: | Size: 940 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_forward_white.png
Normal file
|
After Width: | Height: | Size: 827 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_full.png
Normal file
|
After Width: | Height: | Size: 675 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_full_white.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_lost.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_moveback_click.png
Normal file
|
After Width: | Height: | Size: 962 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_moveback_white.png
Normal file
|
After Width: | Height: | Size: 850 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_nav_back.png
Normal file
|
After Width: | Height: | Size: 452 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_play.png
Normal file
|
After Width: | Height: | Size: 943 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_play_white.png
Normal file
|
After Width: | Height: | Size: 753 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_player.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_scan.png
Normal file
|
After Width: | Height: | Size: 714 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_scan_click.png
Normal file
|
After Width: | Height: | Size: 710 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_set.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_set_click.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_slow_click.png
Normal file
|
After Width: | Height: | Size: 263 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_slow_white.png
Normal file
|
After Width: | Height: | Size: 245 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_snapshot_click.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_snapshot_white.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_splash_bg.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_stop.png
Normal file
|
After Width: | Height: | Size: 924 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_stop_white.png
Normal file
|
After Width: | Height: | Size: 714 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_stretch_click.png
Normal file
|
After Width: | Height: | Size: 409 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_stretch_white.png
Normal file
|
After Width: | Height: | Size: 344 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_thumb.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_top_bg.png
Normal file
|
After Width: | Height: | Size: 521 B |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_version1.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_version2.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/new_version3.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xhdpi/placeholder.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_address.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_address_click.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_fast_click.png
Normal file
|
After Width: | Height: | Size: 748 B |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_fast_white.png
Normal file
|
After Width: | Height: | Size: 715 B |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_file.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_file_click.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_forward_click.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_forward_white.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_full.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_full_white.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_lost.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_nav_back.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_play.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_play_white.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_player.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_scan.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_scan_click.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_set.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_set_click.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_slow_click.png
Normal file
|
After Width: | Height: | Size: 425 B |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_slow_white.png
Normal file
|
After Width: | Height: | Size: 421 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
BIN
EasyPlayerPro/src/main/res/drawable-xxhdpi/new_splash_bg.png
Normal file
|
After Width: | Height: | Size: 19 KiB |