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
相关产品推荐
相关产品推荐

