init project

This commit is contained in:
leo
2023-10-07 21:31:31 +08:00
commit 6f71dbf2d3
219 changed files with 13113 additions and 0 deletions

1
EasyPlayerPro/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View 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')
}

Binary file not shown.

30
EasyPlayerPro/proguard-rules.pro vendored Normal file
View 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{
*;
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}
}
}

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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");
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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) {
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 975 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Some files were not shown because too many files have changed in this diff Show More