Flutter进阶:全局音视频流进入后台停止播放

432 阅读2分钟

应用程序生命周期改变全局停止所有正在运行的音频和视频流?

商业级 Flutter 应用中因为场景的多样性,很难避免其中包含多个音频和视频播放器(例如 video_player、just_audio、audioplayers、audio_session)。当应用生命周期发生变化时(例如,应用转入后台或分离),需要停止或暂停所有正在运行的媒体流,而无需手动管理每个音频或视频实例。

解决办法 1:

在每个播放组件的 didChangeAppLifecycleState() 中暂停每个实力。使用生命周期钩子进行手动管理,但是很麻烦。如果要二次调整停止流媒体时机,每个地方都需要修改,维护性差。

解决办法 2:

通过订阅模式持有方法指针,全局统一管理,完美解决问题。

二、订阅模式管理音视频流管理实现

1、实现音视频管理单例类

import 'dart:io';

import 'package:audio_session/audio_session.dart';
import 'package:flutter/foundation.dart';

/// AudioSession 音视频管理类
class AudioSessionManager {
  static final AudioSessionManager _instance = AudioSessionManager._();
  AudioSessionManager._();
  factory AudioSessionManager() => _instance;


  /// 监听列表(实现音频统一管理)
  final List<AudioSessionSoundPlayerModel> _listeners = [];

  // 添加监听
  void addListener(AudioSessionSoundPlayerModel cb) {
    if (_listeners.contains(cb)) {
      return;
    }
    _listeners.add(cb);
  }

  // 移除监听
  void removeListener(AudioSessionSoundPlayerModel cb) {
    _listeners.remove(cb);
  }

  // 通知所有监听器
  Future<void> notifyListeners(Future<void> Function(AudioSessionSoundPlayerModel e) action) async {
    for (var ltr in _listeners) {
      await action(ltr);
    }
  }

  void clearListeners() {
    _listeners.clear();
  }
}

class AudioSessionSoundPlayerModel {
  AudioSessionSoundPlayerModel({
    this.data,
    this.onPlay,
    this.onStop,
  });

  /// 唯一值
  Map<String, dynamic>? data;

  /// 播放
  Future<void> Function()? onPlay;

  /// 停止播放
  Future<void> Function()? onStop;

  AudioSessionSoundPlayerModel.fromJson(Map<String, dynamic>? json) {
    if (json == null) {
      return;
    }
    data = json['data'] ?? {};
    onPlay = json['onPlay'];
    onStop = json['onStop'];
  }

  Map<String, dynamic> toJson() {
    final data = Map<String, dynamic>();
    data['data'] = data;
    data['onPlay'] = onPlay.hashCode;
    data['onStop'] = onStop.hashCode;
    return data;
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }

    final isEqual = other is AudioSessionSoundPlayerModel &&
        runtimeType == other.runtimeType &&
        mapEquals(toJson(), other.toJson());
    return isEqual;
  }

  @override
  int get hashCode => data.hashCode ^ onPlay.hashCode ^ onStop.hashCode;
}

2、注册到 AudioSessionManager 示例:

class SoundPlayerAndRecorderState extends State<SoundPlayerAndRecorder>
    with AutomaticKeepAliveClientMixin, SafeSetStateMixin {
 
  /// current audio model
  AudioSessionSoundPlayerModel get audioSessionSoundPlayerModel => AudioSessionSoundPlayerModel(
    data: widget.model?.toJson(),
    onPlay: onPlay,
    onStop: onStop,
  );

...

  Future<void> onPlay() async {
    await AudioSessionManager().notifyListeners((e) async {
      await e.onStop?.call();
    });

    AudioSessionManager().addListener(audioSessionSoundPlayerModel);

   //your audio play codes
  
  }


  Future<void> onStop() async {
     //your audio stop play codes

    AudioSessionManager().removeListener(audioSessionSoundPlayerModel);
  }

  ...
}

3、监听app生命周期回调方法中

  switch (state) {
    case AppLifecycleState.inactive:
      {
        AudioSessionManager().notifyListeners((e) async {
         await e.onStop?.call();// 停止所有音视频播放
       });
      }
      break;
    default:
      debugPrint("$state");
  }

总结

在项目开发中,因为场景众多,很难避免各种疑难问题。这时候就需要我们发散思维,结合现有知识跳出窠臼,提出创造性的解决办法,维护时会省时省力。方法1虽然也能解决问题,但维护性较差,方法2才是兼顾维护性和扩展性的最佳方法。

github