文章目录
接入FFmpeg
1.下载FFmpeg 源码
2.编译FFmpeg.so库
- 移动 FFmpeg 源码文件夹至 Android Studio 的 cpp 包下(也可以不移)
- 在 FFmpeg 文件夹内创建用来编译 .so 库 的 sh脚本
编译脚本是基于以下 Android 各较新版本:
库 | 版本 |
---|---|
Android SDK | 35 |
NDK | 26 |
CMake | 3.6 |
JDK | 11 |
Gradle | 8.11.0 |
Android Gradle Plugin | 8.6.0 |
#!/usr/bin/env bash
# 声明脚本解释器为 bash
# -------------------- 配置路径 --------------------
# ‼️修改成自己的NDK版本和路径
NDK=/Users/xxx/Library/Android/sdk/ndk/26.1.10909125
# Android NDK 路径
HOST_TAG=darwin-x86_64
# 主机系统标识,macOS 64位
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/$HOST_TAG
# LLVM 交叉编译工具链路径
SYSROOT=$TOOLCHAIN/sysroot
# sysroot 路径,NDK 中包含标准库等文件的根目录
API=21
# Android API 级别
FFMPEG_SRC=$(pwd)
# 当前工作目录,即 ffmpeg 源码根目录
OUTPUT_ROOT=$FFMPEG_SRC/android-build
# 编译输出目录
ARCHS=("armeabi-v7a" "arm64-v8a" "x86_64")
# 需要编译的架构列表
# -------------------- 函数:编译单个架构 --------------------
build_one() {
ARCH=$1
# 接收参数,架构名称
echo "==================== 编译架构: $ARCH ===================="
# 打印当前编译的架构,方便查看进度
case $ARCH in
armeabi-v7a)
TARGET=armv7a-linux-androideabi
# armeabi-v7a 架构对应的目标三元组
;;
arm64-v8a)
TARGET=aarch64-linux-android
# arm64-v8a 架构对应的目标三元组
;;
x86_64)
TARGET=x86_64-linux-android
# x86_64 架构对应的目标三元组
;;
*)
echo "❌ 未知架构: $ARCH"
exit 1
# 如果传入架构不在已知列表,退出脚本
;;
esac
CC="$TOOLCHAIN/bin/clang --target=${TARGET}${API} --sysroot=$SYSROOT"
# 定义 C 编译器,带上目标三元组和 API 版本,指定 sysroot
CXX="$TOOLCHAIN/bin/clang++ --target=${TARGET}${API} --sysroot=$SYSROOT"
# 定义 C++ 编译器,参数同上
PREFIX=$OUTPUT_ROOT/$ARCH
# 该架构编译后文件的安装目录
mkdir -p $PREFIX
# 创建安装目录,若不存在则新建
cd $FFMPEG_SRC
# 进入 ffmpeg 源码目录,准备开始编译
make clean
# 清理之前的编译结果,避免干扰
./configure \
--prefix=$PREFIX \
--target-os=android \
--arch=$ARCH \
--enable-cross-compile \
--cc="$CC" \
--cxx="$CXX" \
--sysroot=$SYSROOT \
--enable-shared \
--disable-static \
--disable-doc \
--disable-programs \
--disable-symver \
--disable-debug \
--disable-asm \
--extra-cflags="-Os -fPIC" \
--extra-ldflags=""
# 调用 ffmpeg 的 configure 脚本,配置编译选项:
# --prefix:安装路径
# --target-os=android:目标操作系统为 Android
# --arch:目标架构
# --enable-cross-compile:启用交叉编译
# --cc 和 --cxx:指定 C 和 C++ 编译器
# --sysroot:指定 sysroot 路径
# --enable-shared:生成动态库(so)
# --disable-static:不生成静态库
# --disable-doc:不生成文档
# --disable-programs:不编译 ffmpeg 命令行工具
# --disable-symver:关闭符号版本控制
# --disable-debug:关闭调试
# --disable-asm:禁用汇编优化(可根据需求开启)
# --extra-cflags:额外的编译参数,此处优化大小且使用 PIC
# --extra-ldflags:额外的链接参数,当前为空
if [ $? -ne 0 ]; then
echo "❌ 配置失败: $ARCH"
exit 1
fi
# 如果 configure 出错,则打印失败信息并退出
make -j$(sysctl -n hw.ncpu)
# 多线程编译,线程数为当前 CPU 核数
make install
# 安装编译结果到指定目录
}
# -------------------- 循环编译所有架构 --------------------
for ARCH in "${ARCHS[@]}"; do
build_one $ARCH
# 按顺序调用函数,编译每个架构
done
echo "==================== 全部架构编译完成 ===================="
# 全部架构编译结束,打印提示
- 编译成功后在路径下可以看到生成的 .so 库和 .h 头文件
cpp/ffmpeg-7.1.1/android-build/arm64-v8a/lib .so库位置
cpp/ffmpeg-7.1.1/android-build/arm64-v8a/include .h 头文件
一共输出7个 .so库:
so库 | 功能 |
---|---|
libavcodec.so | 编解码器核心库 |
libavformat.so | 负责封装/解封装(容器格式,如 mp4、mkv) |
libavutil.so | 工具库(数学、字节序、日志等) |
libswscale.so | 视频像素格式转换(YUV ↔ RGB 等) |
libswresample.so | 音频重采样、通道布局转换 |
libavfilter.so | 滤镜处理(裁剪、特效等) |
libavdevice.so | 设备输入输出(通常可以不用) |
编译.so库完成。
异常处理
- 报错:nasm/yasm not found or too old. Use --disable-x86asm for a crippled build.
安装 nasm
brew install nasm
- 报错:C compiler test failed.
- 在系统NDK路径确认 clang 编译路径的有效性
$NDK/toolchains/llvm/prebuilt/darwin-arm64/bin/clang
- 在系统NDK路径确认 clang 编译路径的有效性
3.自定义FFmpeg交互so库创建
创建一个Java类,自定义调用 native 层的方法
package com.example.video; public class FFmpegLoader { //自定义方法,返回FFmpeg的版本,判断接入成功 public native String getFFmpegVersion(); static { //自定义so库名 System.loadLibrary("native-lib-myFFmpeg"); } }
通过命令行在当前路径中生成 .h 文件,再放入cpp文件夹中
javac -h ./jni FFmpegLoader.java
.h文件内容:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_video_FFmpegLoader */ #ifndef _Included_com_example_video_FFmpegLoader #define _Included_com_example_video_FFmpegLoader #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_video_FFmpegLoader * Method: getFFmpegVersion * Signature: ()V */ JNIEXPORT void JNICALL Java_com_example_video_FFmpegLoader_getFFmpegVersion (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
移入路径:cpp/com_example_video_FFmpegLoader.h
在同一cpp路径下,创建 cpp 文件,会有 #include <libavcodec/avcodec.h> 等文件报红,是因为没有配置CMakeLists.txt 链接,可以先不管,或者先注释掉 FFmpge 库的引用,编译完之后再调用。
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_video_FFmpegLoader */ #include <string> extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/avutil.h> } #ifndef _Included_com_example_video_FFmpegLoader #define _Included_com_example_video_FFmpegLoader #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_video_FFmpegLoader * Method: getFFmpegVersion * Signature: ()V */ JNIEXPORT jstring JNICALL Java_com_example_video_FFmpegLoader_getFFmpegVersion (JNIEnv *env, jobject){ const char *version = av_version_info(); return env->NewStringUTF(version); } #ifdef __cplusplus } #endif #endif
4.配置CMakeLists.txt
外部so库调用 :将编译成功的 so 库(路径:cpp/ffmpeg-7.1.1/android-build/arm64-v8a/lib),集中存放到 jni 文件夹下(/src/main/jni)。并且根据 不同架构分别存放,用于之后在CMakeList.txt 中可以动态链接到这些so库。
根据 jni 文件夹相对 CMakeLists.txt 文件的位置,找到各 so 库,通过外部引入。# 引入 FFmpeg 的 so 库 # 1.编解码器核心库 add_library(avcodec SHARED IMPORTED) set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libavcodec.so)
添加头文件目录:让自定义 cpp 文件,可以通过头文件链接到其他 so 库
# 添加头文件目录 include_directories(${CMAKE_SOURCE_DIR}/include)
将自定义 cpp 文件(native-lib-myFFmpeg 库)与FFmpeg 库 链接到一起,使得我的 native 方法可以调用到 FFmpage 的内容。
完整代码:
cmake_minimum_required(VERSION 3.6)
project(ffmpeg_test)
# 添加头文件目录
include_directories(${CMAKE_SOURCE_DIR}/include)
# 引入 FFmpeg 的 so 库
# 1.编解码器核心库
add_library(avcodec SHARED IMPORTED)
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libavcodec.so)
#2.负责封装/解封装(容器格式,如 mp4、mkv)
add_library(avformat SHARED IMPORTED)
set_target_properties(avformat PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libavformat.so)
#3.工具库(数学、字节序、日志等)
add_library(avutil SHARED IMPORTED)
set_target_properties(avutil PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libavutil.so)
#4.视频像素格式转换(YUV ↔ RGB 等)
add_library(swscale SHARED IMPORTED)
set_target_properties(swscale PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libswscale.so)
#5.音频重采样、通道布局转换
add_library(swresample SHARED IMPORTED)
set_target_properties(swresample PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libswresample.so)
#6.滤镜处理(裁剪、特效等)
add_library(avfilter SHARED IMPORTED)
set_target_properties(avfilter PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/../jni/${ANDROID_ABI}/libavfilter.so)
# 添加你的本地库
add_library(native-lib-myFFmpeg SHARED native-lib-myFFmpeg.cpp)
# 链接 FFmpeg 库
target_link_libraries(
native-lib-myFFmpeg
avcodec
avformat
avutil
swscale
swresample
avfilter
android
${log-lib}
)
5.CMakeLists.txt 环境配置
配置如下三点:
- CMakeLists.txt 路径
- ndk 编译架构
- C++编译语言
android {
defaultConfig {
🧠 externalNativeBuild{
cmake{
cppFlags("")
}
}
🧠 ndk{
abiFilters += listOf("armeabi-v7a","x86_64","arm64-v8a")
}
}
🧠 externalNativeBuild{
cmake {
path = file("src/main/cpp/CMakeLists.txt")
}
}
}
build 项目,生成自定义的 native-lib-myFFmpeg.so 文件,路径:
build/intermediates/cxx/Debug/48u475k3/obj/armeabi-v7a/libnative-lib-myFFmpeg.so
6.Native与Java层调用
TextView tv = findViewById(R.id.video_text);
FFmpegLoader fFmpegLoader = new FFmpegLoader();
tv.setText(fFmpegLoader.getFFmpegVersion());
显示FFmpeg的版本,完成FFmpeg 接入。
解码器准备
native-lib-myFFmpeg.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_video_FFmpegLoader */
#ifndef _Included_com_example_video_FFmpegLoader
#define _Included_com_example_video_FFmpegLoader
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_video_FFmpegLoader
* Method: getFFmpegVersion
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_video_SimplePlayer_getFFmpegVersion
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
native-lib-myFFmpeg.cpp
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_video_FFmpegLoader */
#include <string>
#include "NativeSimplePlayer.h"
#include "JNICallbackHelper.h"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
}
#ifndef _Included_com_example_video_FFmpegLoader
#define _Included_com_example_video_FFmpegLoader
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_video_FFmpegLoader
* Method: getFFmpegVersion
* Signature: ()V
*/
JNIEXPORT jstring JNICALL Java_com_example_video_SimplePlayer_getFFmpegVersion
(JNIEnv *env, jobject){
const char *version = av_version_info();
return env->NewStringUTF(version);
}
#ifdef __cplusplus
}
#endif
#endif
//虚拟机可以跨线程
JavaVM *vm = 0;
jint JNI_OnLoad(JavaVM *vm,void *args){
::vm = vm;
return JNI_VERSION_1_6;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_video_SimplePlayer_prepareNative(JNIEnv *env, jobject job, jstring data_source) {
const char* data_source_ = env->GetStringUTFChars(data_source,0);
//可能是主线程,也可能是子线程
auto *helper = new JNICallbackHelper(vm,env,job);
auto *player = new NativeSimplePlayer(data_source_,helper);
player->prepare();
env->ReleaseStringUTFChars(data_source,data_source_);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_video_SimplePlayer_startNative(JNIEnv *env, jobject thiz) {
// TODO: implement startNative()
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_video_SimplePlayer_stopNative(JNIEnv *env, jobject thiz) {
// TODO: implement stopNative()
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_video_SimplePlayer_releaseNative(JNIEnv *env, jobject thiz) {
// TODO: implement releaseNative()
}
NativeSimplePlayer.cpp
//
// Created on 2025/7/22.
//
#include <string.h>
#include "NativeSimplePlayer.h"
NativeSimplePlayer::NativeSimplePlayer(const char *data_source_,JNICallbackHelper *helper) {
//如果被释放,会造成悬空指针
//this->data_source = data_source_;
//深拷贝
//C层:demo.mp4\0 C层会自动 + \0 ,strlen 不计算 \0 的长度
this->data_source = new char[strlen(data_source_) + 1];
strcpy(this->data_source,data_source_);
this->helper = helper;
}
NativeSimplePlayer::~NativeSimplePlayer() {
if( data_source ){
delete data_source;
}
if( helper){
delete helper;
}
}
void* task_prepare(void * args){
//读取文件
auto *player = static_cast<NativeSimplePlayer *>(args);
player->prepare_();
return 0;//必须返回,错误很难找
}
void NativeSimplePlayer::prepare_() {
//为什么FFmpeg源码,大量使用上下文 Context
//因为FFmpge源码是纯C,他没有对象,只能上下文贯穿环境,操作成员变量
/**
* 第一步,打开媒体文件地址
* @parm AVFormatContext -> formatContext 上下文
* @parm filename -> data_source 路径
* @parm AVInputFormat -> *fmt Mac、Windows 摄像头、麦克风
* @param AVDictionary -> 设置Http连接超时,打开rtmp超时
*/
formatContext = avformat_alloc_context();
AVDictionary *dictionary = 0;
av_dict_set(&dictionary,"timeout","5000000",0);
int result = avformat_open_input(&formatContext,data_source,0,&dictionary);
//释放字典
av_dict_free(&dictionary);
if(result){
//回调错误信息给Java,通过JNI反射
return;
}
/**
* 第二步,查找媒体中的音视频流信息
*/
result = avformat_find_stream_info(formatContext,0);
if( result < 0 ){
return;
}
/**
* 第三步,根据流信息,流的个数,用循环来找
*/
// for(int i = 0;i < formatContext->nb_streams;++i){
for(int i = 0;i < 2;++i){
/**
* 第四步,获取媒体流(视频,音频)
*/
AVStream *stream = formatContext->streams[i];
/**
* 第五步,从上面的流中 获取 编码解码的参数
* 由于后面的解码器 编码器 都需要参数(记录的宽高)
*/
AVCodecParameters *parameters = stream->codecpar;
/**
* 第六步,获取编/解码器,根据上面的参数👆
*/
const AVCodec *codec = avcodec_find_decoder(parameters->codec_id);
/**
* 第七步,编解码器 上下文【真正干活】
*/
AVCodecContext *codecContext = avcodec_alloc_context3(codec);
if( !codecContext ){
return;
}
/**
* 第八步,他目前是一张白纸
* 把 parameter 拷贝给=> codecContext
*/
result = avcodec_parameters_to_context(codecContext,parameters);
if( result < 0){
return;
}
/**
* 第九步,打开解码器
*/
result = avcodec_open2(codecContext,codec,0);
if( result ){
return;
}
/**
* 第十步,从编解码器从,获取流的类型 codec_type 决定是音频还是视频
*/
if( parameters->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO){
//分开音频
audio_channel = new AudioChannel();
} else if(parameters->codec_type == AVMediaType::AVMEDIA_TYPE_VIDEO){
//分开视频
video_channel = new VideoChannel();
}
}
/**
* 第十一步,如果流中没有音频,也没有视频 【健壮性校验】
*/
if( !audio_channel && !video_channel){
return;
}
/**
* 第十二步,恭喜你,准备成功,媒体准备完成,通知上层
*/
if(helper){
helper->onPrepared(THREAD_CHILD);
}
}
void NativeSimplePlayer::prepare() {
//此时为Activity调用到的,所以为主线程
//解封装 FFmpeg 来解析,要使用子线程
pthread_create(&pid_prepare,0,task_prepare,this);
}
NativeSimplePlayer.h
//
// Created on 2025/7/22.
//
#ifndef COROUTINE_NATIVESIMPLEPLAYER_H
#define COROUTINE_NATIVESIMPLEPLAYER_H
#include <cstring>
#include <pthread.h>
#include "AudioChannel.h"
#include "VideoChannel.h"
#include "JNICallbackHelper.h"
#include "AudioChannel.h"
#include "VideoChannel.h"
#include "util.h"
extern "C"{ //ffmpeg 是纯C写的,必须采用C的编译方式,否则崩溃
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
};
class NativeSimplePlayer {
private:
char *data_source = 0;//指针需要初始值
pthread_t pid_prepare;
AVFormatContext *formatContext = 0;
AudioChannel *audio_channel = 0;
VideoChannel *video_channel = 0;
JNICallbackHelper *helper =0 ;
public:
NativeSimplePlayer(const char *data_source,JNICallbackHelper *helper);
~NativeSimplePlayer();
void prepare();
void prepare_();
};
#endif //COROUTINE_NATIVESIMPLEPLAYER_H
JNICallbackHelper.h Native层调用Java层
//
// Created on 2025/7/22.
//
#ifndef COROUTINE_JNICALLBACKHELPER_H
#define COROUTINE_JNICALLBACKHELPER_H
#include <jni.h>
#include "util.h"
class JNICallbackHelper {
private:
JavaVM *vm = 0;
JNIEnv *env = 0;
jobject job;
jmethodID jmd_prepared;
public:
JNICallbackHelper(_JavaVM *vm, _JNIEnv *env, jobject jobject);
virtual ~JNICallbackHelper();
void onPrepared(int thread_mode);
};
#endif //COROUTINE_JNICALLBACKHELPER_H
JNICallbackHelper.cpp
//
// Created on 2025/7/22.
//
#include "JNICallbackHelper.h"
JNICallbackHelper::JNICallbackHelper(_JavaVM *vm, _JNIEnv *env, jobject jobject) {
this->vm = vm;
this->env = env;
//全局引用
this->job = env->NewGlobalRef(jobject);
jclass claz = env->GetObjectClass(jobject);
//🌟这里没有赋值
jmd_prepared = env->GetMethodID(claz,"onPrepared","()V");
}
JNICallbackHelper::~JNICallbackHelper() {
vm = 0;
env->DeleteGlobalRef(job);
job = 0;
env = 0;
}
void JNICallbackHelper::onPrepared(int thread_mode) {
if( thread_mode == THREAD_MAIN){
env->CallVoidMethod(job,jmd_prepared);
} else if (thread_mode == THREAD_CHILD){
//子线程不可以跨线程,要用全新的env
JNIEnv *env_child;
vm->AttachCurrentThread(&env_child,0);
env_child->CallVoidMethod(job,jmd_prepared);
vm->DetachCurrentThread();
}
}
util.h
//
// Created on 2025/7/22.
//
#ifndef COROUTINE_UTIL_H
#define COROUTINE_UTIL_H
#define THREAD_MAIN 1
#define THREAD_CHILD 2
#endif //COROUTINE_UTIL_H
CMakeLists.txt 添加调用的 cpp
# 添加你的本地库
add_library(native-lib-myFFmpeg
SHARED
native-lib-myFFmpeg.cpp
JNICallbackHelper.cpp
VideoChannel.cpp
AudioChannel.cpp
NativeSimplePlayer.cpp)
Jave层调用类
package com.example.video;
public class SimplePlayer {
public native String getFFmpegVersion();
static {
System.loadLibrary("native-lib-myFFmpeg");
}
public SimplePlayer() {
}
private String dataSource;
private OnPreparedListener onPreparedListener;
public void setDataSource(String dataSource) {
this.dataSource = dataSource;
}
/**
* 播放器的准备工作
*/
public void prepare(){
prepareNative(dataSource);
}
/**
* 开始播放
*/
public void start(){
startNative();
}
/**
* 停止播放
*/
public void stop(){
stopNative();
}
/**
* 释放资源
*/
public void release(){
releaseNative();
}
public void onPrepared(){
if( onPreparedListener != null){
onPreparedListener.onPrepared();
}
}
public void setOnPreparedListener(OnPreparedListener listener){
onPreparedListener = listener;
}
public interface OnPreparedListener{
void onPrepared();
}
/**
* ======================= native 函数区 ==========================
*/
private native void prepareNative(String dataSource);
private native void startNative();
private native void stopNative() ;
private native void releaseNative() ;
}
Activity 准备视频编码器完成
package com.example.video;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.io.File;
public class VideoActivity extends AppCompatActivity {
private SimplePlayer mPlayer;
public static final String TAG = VideoActivity.class.getName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_video);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
TextView tv = findViewById(R.id.video_text);
//tv.setText(mPlayer.getFFmpegVersion());
initPlayer();
}
private void initPlayer(){
mPlayer = new SimplePlayer();
File videFile = new File(Environment.getExternalStorageDirectory()
+ File.separator + "Download/Mcloud.mp4");
mPlayer.setDataSource(videFile.getAbsolutePath());
//准备成功的回调——C++子线程调用
mPlayer.setOnPreparedListener(new SimplePlayer.OnPreparedListener() {
@Override
public void onPrepared() {
Log.d(TAG, "onPrepared: 准备完成");
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(VideoActivity.this,"准备完成,即将播放",Toast.LENGTH_SHORT).show();
}
});
mPlayer.start();
}
});
}
@Override
protected void onResume() {
super.onResume();
//触发
mPlayer.prepare();
}
@Override
protected void onStop() {
super.onStop();
mPlayer.stop();
}
@Override
protected void onDestroy() {
super.onDestroy();
mPlayer.release();
}
}