最近在做双路视频播放,就是在一个页面播放两个视频。我遇到的问题就是音频焦点冲突问题,在下面说明。
什么是双路视频播放(来自AI)
双路视频播放(Dual-Video Playback),从字面上理解,就是指在一个屏幕或应用界面上,同时解码和渲染两个独立的视频流。
可以把它想象成有两列火车在两条并行的轨道上同时行驶,你可以同时看到两列火车的状态。这两个视频流可以有多种关系和布局方式。
核心特征
两个独立的视频源: 这不是将一个视频画面分割成两半,而是实实在在地处理两个不同的视频文件或网络流。例如,一个是
video_A.mp4
,另一个是video_B.mp4
。同时播放: 两个视频在时间上是同步或并行播放的。播放/暂停/快进等操作可能联动控制两个视频,也可能允许独立控制。
精确同步(关键特性): 在很多场景下,双路视频播放的难点和核心在于保持两个视频画面的精确时间同步。例如,如果视频A的第5秒100毫秒对应的是视频B的第5秒100毫秒,那么在播放时必须保证它们始终对齐,不能出现一个快一个慢的情况。
灵活的布局: 两个视频的画面可以有多种呈现方式:
- 画中画 (Picture-in-Picture, PiP):一个小视频窗口悬浮在一个大视频窗口之上。
- 左右分屏 (Side-by-Side):屏幕一分为二,左边播放一个视频,右边播放另一个。
- 上下分屏 (Top-and-Bottom):屏幕一分为二,上边播放一个视频,下边播放另一个。
- 叠加融合 (Overlay/Blending):一个视频作为背景,另一个半透明地叠加在上面。
常见的应用场景
双路视频播放技术非常有价值,因为它能提供单一视频无法实现的信息维度和交互体验。
应用领域 | 具体场景描述 | 示例 |
---|---|---|
在线教育/网络课程 | 一路视频播放老师的讲课画面,另一路同步播放课件PPT或屏幕操作的录像。 | 网易云课堂、Coursera等平台,老师头像在角落,主屏幕是课件。 |
视频剪辑/后期制作 | 在调色或添加特效时,一路播放原始素材,另一路实时播放处理后的效果,方便进行对比。 | Adobe Premiere Pro, Final Cut Pro 中的对比视图功能。 |
体育赛事直播/分析 | 一路播放赛场的全景视角,另一路播放某个运动员的特写视角或精彩回放。 | 足球比赛中,同时展示球场全景和某个球星的个人镜头。 |
安防监控 | 在一个屏幕上同时显示来自不同摄像头的实时监控画面。 | 大楼的安保中心,监控墙上显示着多个区域的实时视频。 |
无障碍功能 (Accessibility) | 一路播放主视频内容,另一路在角落同步播放手语翻译的视频。 | 很多新闻发布会或官方视频会提供手语翻译版本。 |
3D/VR/360°视频 | 这是一种特殊形式的双路视频,系统为左眼和右眼分别渲染一个视频流,从而创造出立体感和沉浸感。 | YouTube VR, Oculus TV 等 VR 应用。 |
互动视频/游戏 | 允许用户在不同视角或故事线之间切换,系统在后台可能需要同时准备好多个视频流以实现无缝切换。 | 一些互动电影或真人游戏。 |
技术挑战
实现稳定流畅的双路视频播放并非易事,尤其是在移动端。
性能消耗巨大: 解码两个视频流(尤其是高清视频)需要双倍的CPU/GPU计算资源。这会显著增加设备的功耗、发热量,并可能导致卡顿或掉帧。这也是为什么你之前会遇到内存溢出等问题的原因之一,处理双路视频对内存管理的要求更高。
同步难题: 保持两个视频流的精确同步是一个巨大的挑战。网络延迟的抖动(Jitter)、解码速度的差异都可能导致音画或两个画面之间出现偏移。需要复杂的时钟同步机制来校准。
音轨处理: 两个视频通常都有自己的音轨。如何处理音频?
- 只播放其中一个视频的音频?
- 将两个音轨混合(Mix)在一起?
- 让用户自己选择听哪一个?
这都需要在产品设计和技术实现上仔细考虑。
复杂的播放器控制逻辑: 一个播放器现在需要管理两个视频源的状态,播放/暂停、快进/快退、缓冲状态、错误处理等逻辑都变得复杂了一倍。
总而言之,双路视频播放是一项强大的功能,它通过在同一时间呈现更多维度的视觉信息,极大地丰富了应用的功能和用户体验,但同时也对应用的性能优化和技术实现提出了更高的要求。
初方案
`video_player` 插件
创建两个独立的 `VideoPlayerController` 实例,分别控制两个视频的播放。
代码
写在开头,重点就是被注释掉的那句,其实没必要往下看。
VideoPlayerController controller1 = VideoPlayerController.networkUrl(
Uri.parse(videoUrl),
//videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), //是的,一开始没加这句!!!这就是问题所在
);
VideoPlayerController controller2 = VideoPlayerController.networkUrl(
Uri.parse(videoUrl),
//videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
);
组件代码,只是简单的模拟
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
children: <Widget>[
Expanded(
child: _controller1.value.isInitialized
? AspectRatio(
aspectRatio: _controller1.value.aspectRatio,
child: VideoPlayer(_controller1), //这里
)
: Container(),
),
Expanded(
child: _controller2.value.isInitialized
? AspectRatio(
aspectRatio: _controller2.value.aspectRatio,
child: VideoPlayer(_controller2),//这里
)
: Container(),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
togglePlayPauseBoth();
},
child: Icon(
_controller1.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}
void setVolumeBoth(double volume) {
controller1?.setVolume(volume); //注意这里少了一个
}
void togglePlayPauseBoth() {
final bool isPlaying = controller1?.value.isPlaying ?? false;
if (isPlaying) {
pauseBoth();
} else {
playBoth();
}
}
void playBoth() {
controller1?.play();
controller2?.play();
}
void pauseBoth() {
controller1?.pause();
controller2?.pause();
}
void seekBoth(Duration position) {
controller1?.seekTo(position);
controller2?.seekTo(position);
}
问题描述
点击播放只有第一个视频播放,而且播放按钮play_arrow -> pause -> play_arrow。
原因分析
经过了一些不必要的弯路,我终于看到了日志
D/AudioManager(31189): dispatching onAudioFocusChange(-1) ...
这一行日志就是问题的“确凿证据”。它告诉我们,系统正在向您的应用发送一个“音频焦点丢失” (AUDIOFOCUS_LOSS
) 的通知。
问题根源:音频焦点冲突
- 什么是音频焦点? 在Android系统中,为了避免多个应用同时播放声音造成混乱,只有一个应用可以在同一时间“持有”音频焦点。
- 发生了什么? 当您调用
playBoth()
时,视频播放器1(controller1
)和视频播放器2(controller2
) 同时向系统请求音频焦点。 - 系统如何反应? Android系统看到来自同一个应用的第二个音频请求,会认为第一个请求应该被放弃。因此,它会立即给第一个请求者发送一个
AUDIOFOCUS_LOSS
通知。 video_player
插件的默认行为:video_player
插件在收到“音频焦点丢失”的通知后,会非常“懂事”地自动暂停播放。
其他方案
1、使用 `multi_video_player` 插件
2、使用 `video_player_mux` 插件
怎么使用我也只看了官网例子,没有实践,有空补上。
一些废话,记录bug解决过程
没有发日志之前AI越走越弯
问题1:
一开始这里出现的问题是点击播放只有第一个视频播放,而且播放按钮play_arrow -> pause -> play_arrow。
一开始没给AI日志信息,所以分析是两个控制器状态不统一,给出了解决方案1。
解决方案1:
核心是将播放/暂停的命令统一处理,并建立一个单向的同步机制:始终以后置视频的状态为准,强制前置视频跟随。
问题2:
由于上诉修改产生了问题2。因为强制两个视频状态同步,主打同生共死,所以两个视频他们一起停了,按钮play_arrow -> pause -> play_arrow。
解决方案2:
撤回解决方法1。
后面还在ai的指导下尝试了
1、将一个视频静音---->没用。
2、只播放一个视频永远只让一个播放器(主播放器)真正执行play()
和pause()
操作,另一个播放器(从播放器)只通过seekTo()
来被动地同步画面,从而绝不请求音频焦点。---->卡顿非常明显,pass。