You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter播放M3U8流触发PlatformException(-11819)错误求助

M3U8无限音乐流播放数分钟后停止,抛出PlatformException(-11819)异常

我给应用播放器配置了不同频道的M3U8无限音乐流,用HlsAudioSource加载对应URL,初始播放正常,但数分钟后播放器会停止,并且在playbackState流中捕获到PlatformException(-11819, Cannot Complete Action, null, null)异常。


相关代码

1. AudioService初始化与自定义AudioHandler

Future<AudioHandler> initAudioService() async {
  return await AudioService.init(
    builder: () => MyAudioHandler(),
    config: const AudioServiceConfig(
      androidNotificationChannelId: 'io.test.testMusic',
      androidNotificationChannelName: 'testMusic',
      androidNotificationOngoing: true,
      androidStopForegroundOnPause: true,
    ),
  );
}

class MyAudioHandler extends BaseAudioHandler {
  final AudioPlayer _player = AudioPlayer();

  MyAudioHandler() {
    _player.playbackEventStream.map(_transformEvent).pipe(playbackState);
  }

  @override
  Future<void> playFromUri(Uri uri, [Map<String, dynamic>? extras]) async {
    mediaItem.add(MediaItem(id: uri.toString(), title: 'MoodStream'));
    print('url=====${uri.toString()}');
    try {
      _player.setAudioSource(
        HlsAudioSource(uri
            // Uri.parse(
            //   'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8',
            // ),
            ),
      );
    } on PlayerException catch (e) {
      print('exception==$e');
      return;
    }

    return; //_player.playing ? _player.pause() : _player.play();
  }

  @override
  Future<void> click([MediaButton button = MediaButton.media]) async {
    print('button===${button}');
    switch (button) {
      case MediaButton.media:
        {
          if (playbackState.valueOrNull?.playing == true) {
            await _player.pause();
          } else {
            await _player.play();
          }
          break;
        }
      case MediaButton.next:
      // TODO: Handle this case.
      case MediaButton.previous:
      // TODO: Handle this case.
    }
    return super.click(button);
  }

  @override
  Future<void> play() async {
    await _player.play();
    return super.play();
  }

  @override
  Future<void> pause() async {
    await _player.pause();
    return super.pause();
  }

  @override
  Future<void> stop() async {
    await _player.stop();
    return super.stop();
  }

  PlaybackState _transformEvent(PlaybackEvent event) {
    return PlaybackState(
        controls: [
          // MediaControl.rewind,
          if (_player.playing) MediaControl.pause else MediaControl.play,
          //MediaControl.stop,
          // MediaControl.fastForward,
        ],
        // systemActions: const {
        //   MediaAction.seek,
        //   MediaAction.seekForward,
        //   MediaAction.seekBackward,
        // },
        androidCompactActionIndices: const [
          0,
          1,
          3
        ],
        processingState: const {
          ProcessingState.idle: AudioProcessingState.idle,
          ProcessingState.loading: AudioProcessingState.loading,
          ProcessingState.buffering: AudioProcessingState.buffering,
          ProcessingState.ready: AudioProcessingState.ready,
          ProcessingState.completed: AudioProcessingState.completed,
        }[_player.processingState]!,
        playing: _player.playing,
        updatePosition: _player.position,
        bufferedPosition: _player.bufferedPosition,
        speed: _player.speed,
        queueIndex: event.currentIndex,
        errorMessage: 'error');
  }
}

2. Bloc中的播放器初始化代码

initPlayerHandler(Uri playerUri) async {
    bool isValid = await isValidPlaylist();
    if (isValid) {
      try {
        if (homeBloc.state.selectedRoom != null) {
          await playerHandler.playFromUri(playerUri);
          if (state.isPlaying) {
            _resumePlay();
          }
          _subscribeToStream();
        }
      } on PlayerException catch (e) {
        print('set player error====$e');
        GlobalSnackBar.show('Error in playlist content');
        emit(state.copyWith(
            playbackStatus: PlaybackStatus.error, isPlaying: false));
        //setPlayer(isFromException: true);
      }
    } else {
      GlobalSnackBar.show('This room does\'t has any playlist.');
    }
  }

3. PlaybackState状态监听代码

playerHandler.playbackState.listen(
      (event) async {
        if (event.processingState == AudioProcessingState.buffering) {
          /// bufferring
          emit(state.copyWith(playbackStatus: PlaybackStatus.buffering));
        }
        if (event.processingState == AudioProcessingState.ready) {
          emit(state.copyWith(playbackStatus: PlaybackStatus.playing));
        }

        if (event.playing) {
          if (event.processingState == AudioProcessingState.completed) {
            await setPlayer();
          } else {
            if (event.processingState == AudioProcessingState.idle) {
            } else if (event.processingState == AudioProcessingState.ready) {
              if (state.isPlaying) {
                return;
              }
              emit(state.copyWith(
                  playbackStatus: PlaybackStatus.playing, isPlaying: true));
              if (state.isPlaying) {
                _resumePlay();
              }
            }
          }
        } 
      },
      onError: (e) async {
        print('_subscribeToStream==onError==$e');
        GlobalSnackBar.show(e.message);
        emit(state.copyWith(
            playbackStatus: PlaybackStatus.playing, isPlaying: false));
      },
      onDone: () async {
        await setPlayer();
      },
    );

4. 其他播放控制函数

Future<void> _stopPlayer() async {
    try {
    
      await playerHandler.stop();
      return Future<void>.value();
    } catch (e) {
      print('player error _stopPlayer====$e');
      Future<void>.value();
    }
  }

  _resumePlay() async {
    try {
      await playerHandler.play();
      _onPlayStausChange(3);
    } catch (e) {
      print('player error _resumePlay====$e');
      emit(state.copyWith(
          playbackStatus: PlaybackStatus.error, isPlaying: false));
      GlobalSnackBar.show('Player content exception');
    }
  }

  _pausePlay() async {
    try {
      await playerHandler.pause();
      _onPlayStausChange(1);
      emit(state.copyWith(
          playbackStatus: PlaybackStatus.paused, isPlaying: false));
    } catch (e) {
      print('player error _pausePlay====$e');
      emit(state.copyWith(
          playbackStatus: PlaybackStatus.error, isPlaying: false));
    }
  }

Flutter环境信息

[✓] Flutter (Channel stable, 3.13.9, on macOS 14.1 23B74 darwin-arm64, locale en-AE)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.2)
[✓] Xcode - develop for iOS and macOS (Xcode 15.0)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2022.3)
[✓] VS Code (version 1.84.0)
[✓] Connected device (4 available)
[✓] Network resources

Android设备日志

D/BufferPoolAccessor2.0( 5497): bufferpool2 0xb4000073b9599e28 : 5(40960 size) total buffers - 1(8192 size) used buffers - 18216/18221 (recycle/alloc) - 5/36438 (fetch/transfer)
D/BufferPoolAccessor2.0( 5497): bufferpool2 0xb4000073b9599e28 : 5(40960 size) total buffers - 4(32768 size) used buffers - 18246/18251 (recycle/alloc) - 5/36492 (fetch/transfer)
D/BufferPoolAccessor2.0( 5497): evictor expired: 1, evicted: 1
D/TrafficStats( 5497): tagSocket(171) with statsTag=0xffffffff, statsUid=-1

问题分析与修复方案

1. 异常含义解析

PlatformException(-11819)对应iOS AVFoundation的AVErrorCannotComplete错误,即使在Android设备上出现,也是底层播放器(如ExoPlayer)映射的同类错误,核心原因是无法完成流加载操作,常见于HLS分段获取失败、网络中断或流本身异常。

2. 具体修复措施

(1)排查HLS流本身的稳定性

  • 用你注释的公共测试HLS流替换当前URL,测试是否仍会停止,排除自家服务器或流生成的问题。
  • 确认无限流服务器是否持续生成新的TS分段,若服务器停止推送,播放器会因无后续分段而终止。

(2)优化HLS缓冲配置

当前代码未设置缓冲参数,默认缓冲可能不足以应对网络波动,给HlsAudioSource添加缓冲配置:

HlsAudioSource(
  uri,
  bufferConfiguration: const BufferConfiguration(
    minBufferDuration: Duration(seconds: 30),
    maxBufferDuration: Duration(minutes: 1),
    bufferForPlaybackDuration: Duration(seconds: 10),
    bufferForPlaybackAfterRebufferDuration: Duration(seconds: 20),
  ),
)

(3)完善错误捕获与状态传递

  • 修改_transformEvent中的错误信息传递,替换硬编码的errorMessage: 'error':
    errorMessage: event.errorMessage,
    
  • 新增播放器层面的错误监听,不依赖playbackState的onError:
    MyAudioHandler() {
      _player.playbackEventStream.map(_transformEvent).pipe(playbackState);
      _player.playerErrorStream.listen((error) {
        print('Player底层错误: ${error.message}');
        // 此处可触发UI层错误提示或重连逻辑
      });
    }
    

(4)添加异常重连逻辑

在playbackState的onError中针对-11819异常做延迟重连:

onError: (e) async {
  print('_subscribeToStream==onError==$e');
  if (e is PlatformException && e.code == '-11819') {
    await Future.delayed(const Duration(seconds: 5));
    await initPlayerHandler(playerUri); // 重新加载当前流
    return;
  }
  GlobalSnackBar.show(e.message);
  emit(state.copyWith(playbackStatus: PlaybackStatus.error, isPlaying: false));
},

(5)调整前台服务配置

当前androidStopForegroundOnPause: true可能导致缓冲暂停时服务被系统回收,改为false确保服务常驻前台:

config: const AudioServiceConfig(
  androidNotificationChannelId: 'io.test.testMusic',
  androidNotificationChannelName: 'testMusic',
  androidNotificationOngoing: true,
  androidStopForegroundOnPause: false,
),

内容的提问来源于stack exchange,提问作者dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 00:45:56