播放器接口

 

Hello World 一节中,我们已经通过一个简单的 Demo 实现了最基础的播放器使用。
本节将对播放器接口进行完整地、系统地介绍。 如果你还尚未阅读 播放器状态 一节中的内容,请先跳转过去进行阅读。 了解播放器状态的内容将能让你对播放器接口的调用有更加清晰的认识。

FijkPlayer 概念

这里有必要再次啰嗦一番,FijkPlayer 是库 fijkplayer 中的一个 dart class。在这个 FijkPlayer 背后到底存在什么, 赋予了它媒体播放的能力?

FijkPlayer 是对 ijkplayer 的 Flutter 封装。 FijkPlayer 与 native 中的 ijkplayer 一一对应。 同一个播放器存在与多个语言环境中并且一一对应。

C 环境中的 FFPlayer 或者 IjkMediaPlayer

java 环境中的 IjkMediaPlayer 或 objc 环境中的 IJKFFMediaPlayer

dart 环境中的 FijkPlayer

这三层播放器封装通过语言间通讯接口进行通讯,一一对应。其中 C 语言层的 FFPlayer 负责最核心的播放器实现,启动多个不同的播放器线程,读取文件、网络数据,解码等。其他两层都是是接口封装,方便在更高级的语言层面使用播放器。

C 层的播放器自己进行内存管理,而其他两层则都依赖垃圾回收机制进行内存管理。在整个 java IjkMediaPlayer 、 objc IJKFFMediaPlayer 、 dart FijkPlayer 对象的生命周期中,都只关联一个 C 层的播放器结构,并且在垃圾回收释放内存前,需要显示调用接口释放掉 C 层中的内存和其他资源。 这种单一的对应关系也避免一下概念上的错误,有助于对项目、代码逻辑的理解。

BTW,这一点和 [Bilibili/ijkplayer] 还有所不同,具体和 bilibili ijkplayer k0.8.8 版本进行比较。 在 B站 ijkplayer 中,调用 reset 接口后 C 层的播放器 FFPlayer 以及 IjkMediaPlayer 都会进行销毁重建,对于这一点我不是很清楚为何这样做。
为了实现我上面描述的整个生命周期中一一对应,我进行了部分代码修改,具体 点我 查看

播放器接口

创建播放器

dart api FijkPlayer

FijkPlayer fplayer = FijkPlayer();

播放器 fplayer 创建后,java/objc 层的播放器以及 C 层的播放器也随之一起完成对象创建或 struct 内存分配。终其一生,这个 fplayer 就只能对应这一个 C 层的 FFPlayer。 并且在使用完成之后, fplayer 必须释放其内部 FFPlayer 所占用的资源。

销毁

dart api release

Future<void> release() async
// usage
fplayer.release();

release 方法内部会根据当前的播放器状态判断是否需要调用 stop 接口。 所以可以在播放器的任何状态下调用 release 接口进行资源释放。

播放控制

  • 设置资源 dart api setDataSource
    Future<int> setDataSource(String path, {bool autoPlay = false,}) async
    
    /// usage
    /// autoPlay 为 true 时等同于连续调用 setDataSource、prepareAsync、start
    fplayer.setDataSource("http://samplevideo.com/sample.flv", autoPlay: true);
    ///
    /// 设置本地资源作为播放源,
    /// pubspec.yml 中需要指定assets 内容
    ///   assets:
    ///     - assets/butterfly.mp4
    ///
    /// scheme 是 `asset`, `://` 是 scheme 分隔符, `/` 是路径起始符号
    fplayer.setDataSource("asset:///assets/butterfly.mp4", autoPlay: true);
    

    设置播放器播放资源,接口内部会尝试自动判断资源类型,比如网络资源,本地文件资源等。 可选参数 autoPlay 设置为 true 时会在设置资源后调用 prepareAsyncstart 接口自动播放。

  • 异步加载
    dart api prepareAsync
    Future<void> prepareAsync() async
    
    // usage
    fplayer.prepareAsync();
    

    控制播放器开始启动各种资源,解析媒体内容,准备进行媒体播放。 函数调用结束后,播放器状态变为 asyncPreparing,等待准备工作完成后,播放器状态会变为 prepared。 调用次方法前必须先设置 dataSource。

  • 开始
    dart api start
    Future<void> start() async
    
    // usage
    fplayer.start();
    

    控制播放器开始播放。

  • 暂停
    dart api pause
    Future<void> pause() async
    
    // usage
    fplayer.pause();
    

    控制播放器进入暂停状态。

  • 停止
    dart api stop
    Future<void> stop() async
    
    // usage
    fplayer.stop();
    

    控制播放器终止播放,stop 之后不可以直接通过 start 接口进入播放状态。

  • 重置
    dart api reset
    Future<void> reset() async
    
    // usage
    fplayer.reset();
    

    重置播放器进入 idle 状态,可以再次 setDataSource

  • 进度跳转
    dart api seekTo
    Future<void> seekTo() async
    
    // usage
    fplayer.seekTo(3000);
    

    控制播放器进行播放进度,参数以毫秒为单位。只能在可播放状态下调用此接口,参考 可播放状态

播放器状态控制接口都是 async 异步接口。如果需要连续调用多个播放器控制接口,需要使用 await 或者 Future.then() 等待异步完成。 不等待前一个调用完成就直接调用后一个方法可能会出现状态错误问题。

其他接口

  • 调节音量 dart api
    Future<void> setVolume() async
    
    fplayer.setVolume(0.5);
    

    音量设置范围在 [0.0, 1.0],设置超出此范围的音量只效果无法保障。

  • 设置播放循环 dart api
    Future<void> setLoop(int loopCount) async
    
    fplayer.setLoop(10);
    

    默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。

  • 设置播放倍速 dart api
    Future<void> setSpeed(double speed) async
    
    fplayer.setSpeed(1.5);
    

    倍速调整不宜过大,请根据实际效果设定合适的倍速值。

  • 修改全屏状态
    void enterFullScreen()
    void exitFullScreen()
    

    这两个方法仅仅是修改了播放器中的一个属性,UI 上的变化需要 FijkView 去监听这个属性的变化而作出改变。

设置播放器属性

设置播放器的属性属于高级用法。
这里介绍设置属性的方法,以及和播放器相关的主要属性。

设置属性方法同时可用于设置 ffmpeg 属性, 比如 avcodec avformat avresmple 相关属性等,这里的文档无法提供全部内容。更多关于 ffmpeg 的属性请去阅读 ffmpeg 文档,或者去搜索引擎查找资料。

设置播放器属性需要在调用 prepareAsync() 之前,否则设置仅被保存下来,但不会应用到相应使用此属性值的执行逻辑中。 调用 reset() 之后,已经设置的 option 不会被重置,依然有效。

如果在 prepareAsync() 之后设置了 option 的值,那么这些值会在此播放器下次调用 prepareAsync() 的时候生效。

单次属性设置

单次属性设置使用 FijkPlayer class 的方法。
参数 category 使用 FijkOption class 中的静态常量 formatCategory, codecCategoryswsCategory, playerCategory, swrCategory

Future<void> setOption(int category, String key, dynamic value);

value 可以是 String 或者 int 类型,分别用于设置字符串类型和整型的播放器属性。

批量属性设置

批量设置播放器属性使用 applyOptions 方法。

Future<void> applyOptions(FijkOption fijkOption) async;

先将所有的播放器属性设置在 dart 对象 fijkOption 中,然后在一次性传递给 native 层。

class FijkOption {
    void setPlayerOption(String key, dynamic value);
    void setFormatOption(String key, dynamic value);
    void setCodecOption(String key, dynamic value);
    void setSwsOption(String key, dynamic value);
    void setSwrOption(String key, dynamic value);
}

dynamic 类型的参数 value 实际上只接受 Stringint 类型,否则会 throw ArgumentError

获取播放器信息

这里将播放器信息的分为 4 类:

  1. 会发生变化但是变化不频繁的属性
  2. 频繁变化的属性
  3. 一旦确定值后不会发生变化的属性
  4. 无关紧要的属性

前两类属性可以监听值变化,后两类需要调用接口主动获取。

FijkValue

FijkValue 中包含了所有的第一类(会发生变化但是变化不频繁)播放器信息。
FijkPlayer 继承了 ChangeNotifier,可以通过 addListener 获取 FijkValue 的值变化。

当不再需要监听 FijkValue 变化,或者 addListener 回调函数所使用的对象被释放时,必须调用 removeListener 接口移除监听回调。
如果 FijkPlayer 的存活时间长于 addListener 回调函数所在对象的存活时间,一定不要使用匿名闭包函数

class FijkPlayer extends ChangeNotifier implements ValueListenable<FijkValue>
// FijkValue usage,  addListener
@override
void initState() {
    super.initState();
    fplayer.addListener(_fijkValueListener);
}

void _fijkValueListener() {
    FijkValue value = fplayer.value;

    double width = _vWidth;
    double height = _vHeight;

    if (value.prepared) {
        width = value.size.width;
        height = value.size.height;
    }

    if (width != _vWidth || height != _vHeight) {
        setState(() {
            _vWidth = width;
            _vHeight = height;
        });
    }
}

@override
void dispose() {
    super.dispose();
    fplayer.removeListener(_fijkValueListener);
    fplayer.release();
}

通过 FijkValue 可以获取的播放器信息有:

  • prepared 表示 prepareAsync 后台任务是否执行完成,完成后播放器状态也对应转变为 prepared
  • completed 表示播放器是否播放完成,
  • audioRenderStart 表示音频是否开始播放,播放第一帧音频是从 false 变为 true,reset() 之后变为 false
  • videoRenderStart 表示视频是否开始播放,播放第一帧视频是从 false 变为 true,reset() 之后变为 false
  • state 播放器当前状态
  • size 视频分辨率大小
  • duration 媒体内容长度,对于直播内容,duration 值无效
  • fullScreen 播放器是否应该全屏显示,这个属性随着接口 enterFullScreen \ exitFullScreen 的调用而发生变化。
  • exception 播放器进入 error 状态的具体原因。 在错误和异常一节中查看更多内容。

订阅 Stream

第二类 频繁变化的属性 通过订阅 Stream 获取属性值的更新。通过 get 方法获取当前值、初始值。

// 当前缓冲位置
Duration get bufferPos
Stream<Duration> get onBufferPosUpdate

// 当前播放位置
Duration get currentPos
Stream<Duration> get onCurrentPosUpdate 

// 是否数据不足播放出现卡顿,正在缓冲中。
bool get isBuffering
Stream<bool> get onBufferStateUpdate

订阅 Stream ,在结束或者不需要使用时应当取消订阅。比如

StreamSubscription _currentPosSubs;
Duration _currentPos;
@override
void initState() {
    super.initState();
    _currentPos = fplayer.currentPos;
    _currentPosSubs = fplayer.onCurrentPosUpdate.listen((v) {
        setState(() {
            _currentPos = v;
        });
    });
}

@override
void dispose() {
    super.dispose();
    _currentPosSubs?.cancel();
}

其他 get 方法

第三类(一旦确定值后不会发生变化的属性)和第四类信息(无关紧要的属性)就需要通过播放器的其他 get 类方法进行获取。 比如视频解码类型、媒体轨道信息等等。
不过这些目前还没有精力去实现这些方法,后续会补上。

相关的方法后续也会补充在这里,或者查看 dart api 文档页面


您的支持是我的动力。你可以通过以下方式支持我:

每年国庆节前后是临潼石榴集中成熟上市销售的时期,我老丈人家就在秦岭山脉的骊山脚下,家中有十多亩石榴园,全部是绿色种植,施农家肥,人工套袋。 产出的石榴皮薄籽大,甜美多汁。10月份购买的订单均是现摘现发,保证您吃到新鲜美味的石榴。