问题
为了学习Windows编程,最近在看《高级Windows程序设计——中科院(杨力祥)》的网课视频。这个网课制作的比较早,是用三屏方案制作的:

其中,1是授课的视频,2是课件的录屏,3是概要跳转。
在学习的过程中,我发现有几节课里,授课视频和课件录屏不同步,两者有几分钟的延迟,遂开始寻找解决方法。
一节网课的文件目录如下:

其中,
- 授课视频文件,可以用视频播放软件打开,在网页上采用的是Windows Media Player插件控制;
- 控制授课视频的网页文件,授课视频的基本架构在这里,同步控制授课视频和课件录屏的函数也在这里,本次修改只改该文件。
- 课件录屏文件,格式是vga,一种比较早的文件格式,可以用VGAPlayer软件播放,我本次修改遇到的最大阻碍就出在这。
- VGAPlayer插件,如果没有这个网页是解码不了vga文件的,甚至FFmpeg也不行。若网课播放不了,请检查是否缺失该插件。
- 总的网页文件,点击后将入网课界面。
解决方法一(不好用)
找到了授课视频文件和课件录屏文件,我尝试用最简单的方法:分别播放两个文件,通过调整时间来同步,如下图所示:

这样做的缺点很明显,小幅度的同步跳转会很麻烦,调整进度条的时候很麻烦,且丧失了概要跳转功能。
解决方法二(舍弃)
因为这种形式的网课现在已经不常见了,我再遇上这类网课的类似问题的可能性不大,我便想通过取巧的方式快捷地解决这个问题。我开始尝试第二种方法:通过比较两个视频的时长,我发现不同步的问题多是课件录屏的时长比授课视频的时长多了几分钟造成的。有两种解决方案:1,通过剪辑软件增加授课视频的时长;2,通过剪辑软件减少课件录屏的时长。
实践中发现,受我电脑条件的制约,第一种方案在生成目标视频时耗时过长,遂舍弃。电脑可以快速生成剪辑后的视频的朋友可以试一试。
在搜索的时候我发现,vga这种视频格式是由北京翰博尔公司制定的(这是它现在的官网),还没有开源,vga只可以用它自家的软件播放和编辑,其他的播放器(比如potplayer)是无法播放的。可以用vga player播放,据说可以用PowerCreator Media Studio这个软件剪辑,但我找不到PowerCreator Media Studio这个软件的下载地址,只找到了这篇博文,无法进一步验证。
搜索时看到有人说可以用暴风影音编辑vga,尝试后发现是实现不了的。

由于得不到能编辑课件录屏的软件,遂舍弃第二种方案。
解决方法三(通用)
于是我考虑通过阅读源码,直接增加一个同步功能,参照播放器常见的音频同步功能。成品代码在附录部分。
经过阅读源码,我找到了控制授课视频的htm文件——frmleftup.htm,它的源代码在附录部分。它实现授课视频和课件录屏同步播放的思路是:通过定时器,每隔1秒进行检查,当前授课视频的位置秒数是否和课件录屏的位置秒数相差0.3秒以上,若大于0.3秒,则以授课视频的位置秒数为准,将其赋值给课件录屏。这段代码具体为:
<script language="javascript">
function Syn()
{
TimerID = setTimeout("Syn()",1000);
if (Math.abs(parent.RightFrame.VGAPlayer.CurrentPosition - MediaPlayer.Controls.currentPosition * 1000) >= 300)
{
parent.RightFrame.VGAPlayer.CurrentPosition = MediaPlayer.Controls.currentPosition * 1000;
}
if ( (MediaPlayer.playState == mpPlaying) && (parent.RightFrame.VGAPlayer.Status != 2) )
{
parent.RightFrame.VGAPlayer.Play();
}
}
</script>
思路就有了,设置一个变量记录要增加或减少(减少即为负值)的时间差值,在每次检查同步时,将这个变量与授课视频位置秒数的和赋值给课件录屏,就能实现可控秒数的同步。上述段代码就修改为:
<script language="javascript">
//同步授课视频和课件录屏
function Syn()
{
TimerID = setTimeout("Syn()",1000);
//每当授课视频和课件录屏相差300+延时时间(毫秒)时,将两个视频同步(要加上延时时间),以授课视频的时间为准
if (Math.abs(parent.RightFrame.VGAPlayer.CurrentPosition - MediaPlayer.Controls.currentPosition * 1000 - late_second) >= 300)
{
parent.RightFrame.VGAPlayer.CurrentPosition = MediaPlayer.Controls.currentPosition * 1000 + late_second;
}
if ( (MediaPlayer.playState == mpPlaying) && (parent.RightFrame.VGAPlayer.Status != 2) )
{
parent.RightFrame.VGAPlayer.Play();
}
}
</script>
当授课视频的进度条被拉动时,为了保证课件录播也跳到相应的位置,原文件提供的解决办法是:通过响应mediaplayer插件预设的进度条事件,以授课视频拉动后的位置秒数为准,将其赋值给课件录屏。该段代码如下:
<script language="javascript" FOR="MediaPlayer" EVENT="PositionChange(dblOldPosition, dblNewPosition)">
parent.RightFrame.VGAPlayer.CurrentPosition = dblNewPosition * 1000;
</script>
为了能在进度条被拉动时仍能保证可控秒数的同步,要将新位置秒数与时间差值的和赋值给课件录屏。
<script language="javascript" FOR="MediaPlayer" EVENT="PositionChange(dblOldPosition, dblNewPosition)">
//当授课视频的进度条被拉动时,确保课件录屏的时间也会跳转到相应的位置(要加上延迟时间)
parent.RightFrame.VGAPlayer.CurrentPosition = dblNewPosition * 1000 + late_second;
</script>
为了能在网页界面上控制时间差值,要在网页上增加几个控件。我选择了输入框和两个按钮,输入框用来输入时间差值,两个按钮实现对时间差值加一秒或减一秒。默认是隐藏起来的,当需要输入时,鼠标移动到上方便会显示:
<!-- 通过响应鼠标事件来实现显示输入框和按钮,选择空格是为了美观 -->
<p onmouseover='show_input()' onmouseout='save_display()'>
<input id='input1' type="text" style='display:none' value="0">
<button id='but2' style='display:none' onclick='add_second()'>+1s</button>
<button id='but3' style='display:none' onclick='subtract_second()'>-1s</button>
</p>
<script>
//保存延时时间,单位毫秒
var late_second = 0;
//显示输入框和按钮
function show_input(){
document.getElementById('input1').style.display = 'inline';
document.getElementById('but2').style.display = 'inline';
document.getElementById('but3').style.display = 'inline';
}
//隐藏输入框和按钮
//将输入框里获取的字符转换为数字,单位换算为毫秒,赋值给late_second
function save_display(){
document.getElementById('input1').style.display = 'none';
document.getElementById('but2').style.display = 'none';
document.getElementById('but3').style.display = 'none';
late_second = Number(document.getElementById('input1').value)*1000;
}
function add_second(){
document.getElementById('input1').value = Number(document.getElementById('input1').value)+1;
late_second = Number(document.getElementById('input1').value)*1000;
}
function subtract_second(){
document.getElementById('input1').value = Number(document.getElementById('input1').value)-1;
late_second = Number(document.getElementById('input1').value)*1000;
}
</script>
为了能在页面上放下上述控件,我又修改了播放器控件的高度百分比,从100%调到90%:
<script language="javascript">
if (Photo != "")
{
//调整了播放器控件的显示高度,以及对齐方向
document.writeln("<object align=left classid=CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6 id=MediaPlayer width='100%' height='90%'>");
document.writeln(" <param name='windowlessVideo' value='-1'>");
document.writeln(" <param name='stretchToFit' value='-1'>");
document.writeln("</object>");
ShowPhoto(Photo);
}
else
{
//调整了播放器控件的显示高度,以及对齐方向
document.writeln("<object align=left classid=CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6 id=MediaPlayer width='100%' height='90%'>");
document.writeln(" <param name='windowlessVideo' value='0'>");
document.writeln(" <param name='stretchToFit' value='-1'>");
document.writeln("</object>");
}
</script>
默认效果如下:

(鼠标移动到左上角,显示按钮和输入框)

我一般会拉动边框来调整成这个大小:

(鼠标移动到左上角,显示按钮和输入框)

总结
只要是采用了该类三屏解决方案的网课,基本上视频不同步的问题都可以用以上方法解决。
本来想再增加一个功能:刚进入页面执行一次,通过比较两个视频的时长,自动计算差值并同步。但我查找不到使用vga插件获取时长的参考资料,也没有找到官方文档,遂放弃了该功能。
附录
成品代码
以下代码可直接使用,需新创建一个frmleftup.htm文件,将以下代码粘贴到该文件内并保存,再替换掉原本的frmleftup.htm文件。
不知道是什么原因,在csdn的安卓端上以下代码大概率只会显示前83行,83行以后的显示不出来(猜测是代码颜色变成了背景色),但仍能选择、复制。网页端查看以下代码是没有出现上述问题的。
<html>
<head>
<title>Video</title>
<script language="javascript">
if (window.name != "LeftupFrame") location = "content.htm";
</script>
<script src=config.js></script>
<script src=func.js></script>
</head>
<script language="javascript">
var mpStopped=1, mpPaused=2, mpPlaying=3,
mpScanForward=4, mpScanReverse=5, mpEnded=8,
NeverUpdatePosition=1,TimerID=0,
Waiting=0,WaitingCount=0,
AsxFileName;
</script>
<script>
//保存延时时间,单位毫秒
var late_second = 0;
//显示输入框和按钮
function show_input(){
document.getElementById('input1').style.display = 'inline';
document.getElementById('but2').style.display = 'inline';
document.getElementById('but3').style.display = 'inline';
}
//隐藏输入框和按钮
//将输入框里获取的字符转换为数字,单位换算为毫秒,赋值给late_second
function save_display(){
document.getElementById('input1').style.display = 'none';
document.getElementById('but2').style.display = 'none';
document.getElementById('but3').style.display = 'none';
late_second = Number(document.getElementById('input1').value)*1000;
}
function add_second(){
document.getElementById('input1').value = Number(document.getElementById('input1').value)+1;
late_second = Number(document.getElementById('input1').value)*1000;
}
function subtract_second(){
document.getElementById('input1').value = Number(document.getElementById('input1').value)-1;
late_second = Number(document.getElementById('input1').value)*1000;
}
</script>
<script language="javascript" FOR="window" EVENT="onload">
//Load ASF File
if(document.location.protocol == "file:")
AsxFileName = "localclip.asx";
else
AsxFileName = "remoteclip.asx";
MediaPlayer.URL = AsxFileName;
</script>
<script language="javascript">
//同步授课视频和课件录屏
function Syn()
{
TimerID = setTimeout("Syn()",1000);
//每当授课视频和课件录屏相差300+延时时间(毫秒)时,将两个视频同步(要加上延时时间),以授课视频的时间为准
if (Math.abs(parent.RightFrame.VGAPlayer.CurrentPosition - MediaPlayer.Controls.currentPosition * 1000 - late_second) >= 300)
{
parent.RightFrame.VGAPlayer.CurrentPosition = MediaPlayer.Controls.currentPosition * 1000 + late_second;
}
if ( (MediaPlayer.playState == mpPlaying) && (parent.RightFrame.VGAPlayer.Status != 2) )
{
parent.RightFrame.VGAPlayer.Play();
}
}
</script>
<script language="javascript" FOR="MediaPlayer" EVENT="Buffering(Start)">
if (Start)
{
parent.RightFrame.VGAPlayer.Pause();
}
else
{
parent.RightFrame.VGAPlayer.Play();
}
</script>
<script language="javascript" FOR="MediaPlayer" EVENT="playStateChange(NewState)">
switch(NewState)
{
case mpPlaying:
parent.RightFrame.document.all.VGAPlayer.Play();
if (TimerID == 0) Syn();
break;
case mpPaused:
parent.RightFrame.document.all.VGAPlayer.Pause();
break;
case mpStopped:
parent.RightFrame.document.all.VGAPlayer.Stop();
break;
case mpEnded:
parent.RightFrame.document.all.VGAPlayer.Stop();
}
</script>
<script language="javascript" FOR="MediaPlayer" EVENT="PositionChange(dblOldPosition, dblNewPosition)">
//当授课视频的进度条被拉动时,确保课件录屏的时间也会跳转到相应的位置(要加上延迟时间)
parent.RightFrame.VGAPlayer.CurrentPosition = dblNewPosition * 1000 + late_second;
</script>
<body bgcolor="black">
<!-- 通过响应鼠标事件来实现显示输入框和按钮,选择空格是为了美观 -->
<p onmouseover='show_input()' onmouseout='save_display()'>
<input id='input1' type="text" style='display:none' value="0">
<button id='but2' style='display:none' onclick='add_second()'>+1s</button>
<button id='but3' style='display:none' onclick='subtract_second()'>-1s</button>
</p>
<script language="javascript">
if (Photo != "")
{
//调整了播放器控件的显示高度,以及对齐方向
document.writeln("<object align=left classid=CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6 id=MediaPlayer width='100%' height='90%'>");
document.writeln(" <param name='windowlessVideo' value='-1'>");
document.writeln(" <param name='stretchToFit' value='-1'>");
document.writeln("</object>");
ShowPhoto(Photo);
}
else
{
//调整了播放器控件的显示高度,以及对齐方向
document.writeln("<object align=left classid=CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6 id=MediaPlayer width='100%' height='90%'>");
document.writeln(" <param name='windowlessVideo' value='0'>");
document.writeln(" <param name='stretchToFit' value='-1'>");
document.writeln("</object>");
}
</script>
</body>
</html>
frmleftup.htm原本文件的源代码
不知道是什么原因,在csdn的安卓端上以下代码大概率只会显示前83行,83行以后的显示不出来(猜测是代码颜色变成了背景色),但仍能选择、复制。网页端查看以下代码是没有出现上述问题的。
<html>
<head>
<title>Video</title>
<script language="javascript">
if (window.name != "LeftupFrame") location = "content.htm";
</script>
<script src=config.js></script>
<script src=func.js></script>
</head>
<script language="javascript">
var mpStopped=1, mpPaused=2, mpPlaying=3,
mpScanForward=4, mpScanReverse=5, mpEnded=8,
NeverUpdatePosition=1,TimerID=0,
Waiting=0,WaitingCount=0,
AsxFileName;
</script>
<script language="javascript" FOR="window" EVENT="onload">
//Load ASF File
if(document.location.protocol == "file:")
AsxFileName = "localclip.asx";
else
AsxFileName = "remoteclip.asx";
MediaPlayer.URL = AsxFileName;
</script>
<script language="javascript">
function Syn()
{
TimerID = setTimeout("Syn()",1000);
if (Math.abs(parent.RightFrame.VGAPlayer.CurrentPosition - MediaPlayer.Controls.currentPosition * 1000) >= 300)
{
parent.RightFrame.VGAPlayer.CurrentPosition = MediaPlayer.Controls.currentPosition * 1000;
}
if ( (MediaPlayer.playState == mpPlaying) && (parent.RightFrame.VGAPlayer.Status != 2) )
{
parent.RightFrame.VGAPlayer.Play();
}
}
</script>
<script language="javascript" FOR="MediaPlayer" EVENT="Buffering(Start)">
if (Start)
{
parent.RightFrame.VGAPlayer.Pause();
}
else
{
parent.RightFrame.VGAPlayer.Play();
}
</script>
<script language="javascript" FOR="MediaPlayer" EVENT="playStateChange(NewState)">
switch(NewState)
{
case mpPlaying:
parent.RightFrame.document.all.VGAPlayer.Play();
if (TimerID == 0) Syn();
break;
case mpPaused:
parent.RightFrame.document.all.VGAPlayer.Pause();
break;
case mpStopped:
parent.RightFrame.document.all.VGAPlayer.Stop();
break;
case mpEnded:
parent.RightFrame.document.all.VGAPlayer.Stop();
}
</script>
<script language="javascript" FOR="MediaPlayer" EVENT="PositionChange(dblOldPosition, dblNewPosition)">
parent.RightFrame.VGAPlayer.CurrentPosition = dblNewPosition * 1000;
</script>
<body bgcolor="black">
<script language="javascript">
if (Photo != "")
{
document.writeln("<object align=right classid=CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6 id=MediaPlayer width='100%' height='100%'>");
document.writeln(" <param name='windowlessVideo' value='-1'>");
document.writeln(" <param name='stretchToFit' value='-1'>");
document.writeln("</object>");
ShowPhoto(Photo);
}
else
{
document.writeln("<object align=right classid=CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6 id=MediaPlayer width='100%' height='100%'>");
document.writeln(" <param name='windowlessVideo' value='0'>");
document.writeln(" <param name='stretchToFit' value='-1'>");
document.writeln("</object>");
}
</script>
</body>
</html>
本文介绍了一种解决视频课程中授课视频与课件录屏不同步的方法,通过修改源代码,实现了可控秒数的视频同步功能,适用于采用三屏解决方案的网课。
1382

被折叠的 条评论
为什么被折叠?



