ckplayer-plus 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ckplayer.js CHANGED
@@ -93,7 +93,7 @@
93
93
  backLive:false,//显示返回直播按钮
94
94
  seek: 0,//默认需要跳转的秒数
95
95
  next: null,//下一集按钮动作
96
- loaded: '',//加载播放器后调用的函数
96
+ loaded: null,//加载播放器后调用的函数(仅支持 function)
97
97
  plug: '',//使用插件
98
98
  duration:0,//视频总时间
99
99
  preview: null,//预览图片对象
@@ -141,7 +141,7 @@
141
141
  cdnFallback:true// getPath 失败时是否回退到 jsDelivr(内网可设 false,并配置 pluginPath)
142
142
  };
143
143
  // 打包时替换为真实版本;用于 bundler 下 getPath 失效时的 CDN 回退
144
- var ckplayerPlusDefaultCdn='https://cdn.jsdelivr.net/npm/ckplayer-plus@1.1.0/dist/';
144
+ var ckplayerPlusDefaultCdn='https://cdn.jsdelivr.net/npm/ckplayer-plus@1.1.1/dist/';
145
145
  function ckplayerEmbed(videoObj){
146
146
  /*
147
147
  * rightMenu
@@ -174,9 +174,11 @@
174
174
  var seekRetryTimer=null;//续播等待缓冲的重试计时器
175
175
  var seekRetryCount=0;//续播重试次数
176
176
  var seekApplyLock=false;//防止并发多次 currentTime 赋值
177
+ var autoplayPending=false;//自动播放必须等初始化续播完成后才触发
177
178
  var decodeRecoverCount=0;//MEDIA_ERR_DECODE 恢复次数
178
179
  var hlsStartPositionUsed=false;//是否已用 hls.js startPosition 处理续播
179
180
  var hlsResumeTarget=-1;//HLS 续播目标秒数(避免重复 startLoad 取消分片请求)
181
+ var hlsResumeWarmup=false;//HLS 续播先预热首个可解码分片,再定位目标时间
180
182
  var hlsLoadAt=-1;//最近一次 hls.startLoad 目标
181
183
  var hlsLoadTs=0;//最近一次 hls.startLoad 时间戳
182
184
  var oldTime=0,playTime=0,firstSeekTime=-1,maxSeeTime=0;//oldTime=记录上次播放时间,playTime=当前播放时间,firstSeekTime=记录第一次拖动的时间,maxSeeTime=看过的最大时间
@@ -444,7 +446,7 @@
444
446
  }
445
447
  var allowCdn=!vars || vars['cdnFallback']!==false;
446
448
  var cdn=(typeof window!=='undefined' && window.ckplayerPlusCdn) ? window.ckplayerPlusCdn : ckplayerPlusDefaultCdn;
447
- if(!allowCdn || !cdn || cdn.indexOf('1.1.0')>-1){
449
+ if(!allowCdn || !cdn || cdn.indexOf('1.1.1')>-1){
448
450
  // 避免占位版本或显式关闭 CDN 时请求坏地址;交给相对路径或业务 pluginPath
449
451
  return folder+'/'+file;
450
452
  }
@@ -589,8 +591,10 @@
589
591
  player.volume(0);
590
592
  player.muted();
591
593
  recoveryVolume=true;
592
- video.attr('autoplay','autoplay');
593
- paused=false;
594
+ // 不使用原生 autoplay 属性:浏览器会在 seek 前开始 play,随后
595
+ // currentTime / pause 等初始化动作会打断其 Promise,导致永久 loading。
596
+ video.attr('preload','auto');
597
+ autoplayPending=true;
594
598
  }
595
599
  else{
596
600
  video.attr('preload','metadata');
@@ -810,6 +814,7 @@
810
814
  seekApplyLock=false;
811
815
  hlsStartPositionUsed=false;
812
816
  hlsResumeTarget=-1;
817
+ hlsResumeWarmup=false;
813
818
  hlsLoadAt=-1;
814
819
  hlsLoadTs=0;
815
820
  decodeRecoverCount=0;
@@ -985,29 +990,43 @@
985
990
  if(!resumeAt){
986
991
  resumeAt=parseResumeSeekFromVars();
987
992
  }
988
- // 断点续播:startPosition 必须在 loadSource 前设置,从正确 TS 分片起播
993
+ // 部分旧 HLS 的分片不从关键帧开始。直接以续播点 startPosition / startLoad(target)
994
+ // 起播会触发 Chrome PIPELINE_ERROR_DECODE。策略:
995
+ // 1) 从目标前约一个分片开始加载,让前序关键帧进入缓冲
996
+ // 2) 等 hlsCanSafelySeekTo 后再写 currentTime
989
997
  if(resumeAt && resumeAt>0 && (isUndefined(vars['live']) || !vars['live'])){
990
- hlsConf.startPosition=resumeAt;
991
998
  maxSeeTime=Math.max(maxSeeTime,resumeAt);
992
999
  if(firstSeekTime<0){
993
1000
  firstSeekTime=resumeAt;
994
1001
  }
1002
+ seekTime=resumeAt;
995
1003
  hlsResumeTarget=resumeAt;
996
- hlsLoadAt=resumeAt;
997
- hlsLoadTs=Date.now();
998
- seekTime=0;
999
- hlsStartPositionUsed=true;
1000
- clearSeekRetry();
1004
+ hlsResumeWarmup=true;
1005
+ hlsConf.startPosition=hlsPriorLoadTime(resumeAt);
1001
1006
  }
1002
1007
  hls = new Hls(hlsConf);
1003
1008
  hls.loadSource(url);
1004
1009
  hls.attachMedia(video);
1005
1010
  hls.on(Hls.Events.MANIFEST_PARSED, function(){
1011
+ if(hlsResumeWarmup && seekTime>0){
1012
+ try{
1013
+ hls.startLoad(hlsPriorLoadTime(seekTime));
1014
+ }catch(event){}
1015
+ return;
1016
+ }
1006
1017
  if(!hlsStartPositionUsed){
1007
1018
  applyPendingSeek();
1008
1019
  }
1009
1020
  });
1010
1021
  hls.on(Hls.Events.FRAG_BUFFERED, function(event, data){
1022
+ if(hlsResumeWarmup && seekTime>0){
1023
+ // 必须等「目标 + 前序分片」都在同一连续缓冲里,再定位
1024
+ if(hlsCanSafelySeekTo(seekTime)){
1025
+ hlsResumeWarmup=false;
1026
+ applySeekToMedia(seekTime);
1027
+ }
1028
+ return;
1029
+ }
1011
1030
  if(hlsResumeTarget<=0 || !data || !data.frag){
1012
1031
  return;
1013
1032
  }
@@ -1814,6 +1833,38 @@
1814
1833
  }catch(event){}
1815
1834
  return false;
1816
1835
  },
1836
+ /*
1837
+ * hlsPriorLoadTime
1838
+ * 功能:续播加载起点——至少回退约一个分片,保证目标分片前仍有关键帧可参考
1839
+ */
1840
+ hlsPriorLoadTime=function(t){
1841
+ if(isNaN(t) || t<=0){
1842
+ return 0;
1843
+ }
1844
+ return Math.max(0,t-12);
1845
+ },
1846
+ /*
1847
+ * hlsCanSafelySeekTo
1848
+ * 功能:目标已缓冲,且同一连续缓冲区间覆盖目标前约一个分片
1849
+ * 部分旧 TS 分片首帧不是关键帧;若只缓冲目标分片就 seek,Chrome 会报 PIPELINE_ERROR_DECODE
1850
+ */
1851
+ hlsCanSafelySeekTo=function(t){
1852
+ if(!video || isUndefined(video.buffered) || isNaN(t) || t<0){
1853
+ return false;
1854
+ }
1855
+ try{
1856
+ var buf=video.buffered;
1857
+ var needFrom=hlsPriorLoadTime(t);
1858
+ for(var i=0;i<buf.length;i++){
1859
+ var start=buf.start(i);
1860
+ var end=buf.end(i);
1861
+ if(t<=end+0.15 && needFrom>=start-0.35){
1862
+ return true;
1863
+ }
1864
+ }
1865
+ }catch(event){}
1866
+ return false;
1867
+ },
1817
1868
  /*
1818
1869
  * clearSeekRetry
1819
1870
  * 功能:清理续播等待计时器
@@ -1825,6 +1876,35 @@
1825
1876
  }
1826
1877
  seekRetryCount=0;
1827
1878
  },
1879
+ /*
1880
+ * startPendingAutoplay
1881
+ * 功能:续播定位完成后统一发起一次播放,避免 play/pause 竞态
1882
+ */
1883
+ startPendingAutoplay=function(){
1884
+ if(!autoplayPending || !video || !vars['autoplay'] || seekTime || seekApplyLock || loadedmetadataNum<1){
1885
+ return;
1886
+ }
1887
+ autoplayPending=false;
1888
+ player.play();
1889
+ },
1890
+ /*
1891
+ * applySeekToMedia
1892
+ * 功能:仅对已建立解码管线的 media 元素写入 currentTime
1893
+ */
1894
+ applySeekToMedia=function(t){
1895
+ clearSeekRetry();
1896
+ seekApplyLock=true;
1897
+ seekTime=0;
1898
+ try{
1899
+ video.currentTime=t;
1900
+ }catch(event){
1901
+ seekTime=t;
1902
+ seekApplyLock=false;
1903
+ }
1904
+ setTimeout(function(){
1905
+ seekApplyLock=false;
1906
+ },800);
1907
+ },
1828
1908
  /*
1829
1909
  * applyPendingSeek
1830
1910
  * 功能:在媒体就绪且目标时间可解码后再应用挂起的 seekTime
@@ -1853,15 +1933,44 @@
1853
1933
  firstSeekTime=t;
1854
1934
  }
1855
1935
  }
1856
- // HLS:用 hls.startLoad 从目标分片加载(去重,避免取消进行中的 .ts)
1857
- if(hlsSeekTo(t, !!force)){
1858
- clearSeekRetry();
1859
- seekTime=0;
1936
+ // HLS 首分片尚未完成预热时不能直接 startLoad(target),否则某些
1937
+ // 非关键帧分片会让浏览器解码器永久报错。
1938
+ if(hlsResumeWarmup){
1939
+ if(hlsCanSafelySeekTo(t)){
1940
+ hlsResumeWarmup=false;
1941
+ applySeekToMedia(t);
1942
+ }
1860
1943
  return;
1861
1944
  }
1862
- var readyEnough=!isUndefined(video.readyState) && video.readyState>=2;
1863
- var bufferedOk=timeInBuffered(t);
1864
- if(!force && !bufferedOk && (!readyEnough || video.readyState<3)){
1945
+ // HLS:用 hls.startLoad 从目标分片加载(去重,避免取消进行中的 .ts)
1946
+ // 若目标附近尚无前序缓冲,改为预热模式从更早处分片加载
1947
+ if(hls && typeof hls.startLoad==='function'){
1948
+ if(!hlsCanSafelySeekTo(t) && t>1){
1949
+ hlsResumeWarmup=true;
1950
+ hlsResumeTarget=t;
1951
+ try{
1952
+ hls.startLoad(hlsPriorLoadTime(t));
1953
+ }catch(event){}
1954
+ seekRetryCount++;
1955
+ if(seekRetryCount<=50){
1956
+ if(seekRetryTimer){
1957
+ clearTimeout(seekRetryTimer);
1958
+ }
1959
+ seekRetryTimer=setTimeout(function(){
1960
+ applyPendingSeek(false);
1961
+ },200);
1962
+ }
1963
+ return;
1964
+ }
1965
+ if(hlsSeekTo(t, !!force)){
1966
+ clearSeekRetry();
1967
+ seekTime=0;
1968
+ return;
1969
+ }
1970
+ }
1971
+ // 对普通 MP4,metadata 后设置 currentTime 会由浏览器自动请求目标 Range。
1972
+ // 等待目标已缓冲会形成循环等待:目标分片只有 seek 后才会下载。
1973
+ if(!force && (!isUndefined(video.readyState) && video.readyState<1)){
1865
1974
  seekRetryCount++;
1866
1975
  if(seekRetryCount<=50){
1867
1976
  if(seekRetryTimer){
@@ -1873,49 +1982,57 @@
1873
1982
  return;
1874
1983
  }
1875
1984
  }
1876
- clearSeekRetry();
1877
- seekApplyLock=true;
1878
- seekTime=0;
1879
- try{
1880
- video.currentTime=t;
1881
- }catch(event){
1882
- seekTime=t;
1883
- seekApplyLock=false;
1884
- }
1885
- setTimeout(function(){
1886
- seekApplyLock=false;
1887
- },800);
1985
+ applySeekToMedia(t);
1888
1986
  },
1889
1987
  /*
1890
1988
  * recoverDecodeError
1891
1989
  * 功能:断点续播触发的解码错误尝试恢复(Chrome PIPELINE_ERROR_DECODE)
1990
+ * 典型场景:目标 TS 分片首帧不是关键帧,必须回退加载前序分片再建解码管线
1892
1991
  */
1893
1992
  recoverDecodeError=function(){
1894
- if(decodeRecoverCount>=2){
1993
+ if(decodeRecoverCount>=3){
1895
1994
  return false;
1896
1995
  }
1897
1996
  decodeRecoverCount++;
1898
- var resumeAt=playTime>0?playTime:(vars['seek']?parseFloat(vars['seek']):0);
1997
+ var resumeAt=hlsResumeTarget>0?hlsResumeTarget:(playTime>0?playTime:(vars['seek']?parseFloat(vars['seek']):0));
1899
1998
  if(isNaN(resumeAt) || resumeAt<0){
1900
1999
  resumeAt=0;
1901
2000
  }
2001
+ // 每次失败再往前多退一个分片,避免反复落到同一坏点
2002
+ var loadFrom=Math.max(0,resumeAt-(12*decodeRecoverCount));
2003
+ seekApplyLock=false;
2004
+ clearSeekRetry();
1902
2005
  try{
1903
2006
  if(hls && typeof hls.recoverMediaError==='function'){
1904
2007
  hls.recoverMediaError();
1905
- if(resumeAt>0){
1906
- seekTime=resumeAt;
1907
- seekApplyLock=false;
1908
- setTimeout(function(){
1909
- applyPendingSeek(true);
1910
- },300);
1911
- }
1912
- return true;
1913
2008
  }
1914
2009
  }catch(event){}
2010
+ if(resumeAt>0 && hls && typeof hls.startLoad==='function'){
2011
+ seekTime=resumeAt;
2012
+ hlsResumeTarget=resumeAt;
2013
+ hlsResumeWarmup=true;
2014
+ hlsLoadAt=-1;
2015
+ hlsLoadTs=0;
2016
+ try{
2017
+ hls.startLoad(loadFrom);
2018
+ }catch(event){}
2019
+ setTimeout(function(){
2020
+ if(hlsCanSafelySeekTo(resumeAt)){
2021
+ hlsResumeWarmup=false;
2022
+ applySeekToMedia(resumeAt);
2023
+ if(video && video.paused && vars['autoplay']){
2024
+ try{video.play();}catch(e2){}
2025
+ }
2026
+ }
2027
+ else{
2028
+ applyPendingSeek(false);
2029
+ }
2030
+ },500);
2031
+ return true;
2032
+ }
1915
2033
  try{
1916
2034
  var soft=Math.max(0,resumeAt-1.5);
1917
2035
  seekTime=resumeAt;
1918
- seekApplyLock=false;
1919
2036
  if(video){
1920
2037
  video.currentTime=soft;
1921
2038
  }
@@ -1981,6 +2098,7 @@
1981
2098
  C['loading'].hide();
1982
2099
  eventTarget('canplay');
1983
2100
  applyPendingSeek();
2101
+ startPendingAutoplay();
1984
2102
  },
1985
2103
  loadedData:function(){
1986
2104
  eventTarget('loadeddata');
@@ -2036,6 +2154,7 @@
2036
2154
  }
2037
2155
  pSliderMouseDown=false;
2038
2156
  loadedmetadataNum++;
2157
+ startPendingAutoplay();
2039
2158
  if(!isUndefined(ad) && !isUndefined(ad['frontPlay']) && ad['frontPlay'] && loadedmetadataNum>1){//如果是贴片广告播放中,则进行播放和计算
2040
2159
  player.play();
2041
2160
  calculationAdFrontTime(duration);//计算贴片广告的时间
@@ -2119,10 +2238,8 @@
2119
2238
  if(!isUndefined(errorInfo.message)){
2120
2239
  msg=errorInfo.message;
2121
2240
  }
2122
- // code=3 MEDIA_ERR_DECODE:续播非关键帧常见,先尝试恢复
2241
+ // code=3 MEDIA_ERR_DECODE:续播非关键帧常见,先尝试恢复;恢复中不向业务抛致命 error
2123
2242
  if(code===3 && recoverDecodeError()){
2124
- CT.error={code:code,message:msg};
2125
- eventTarget('error',CT.error);
2126
2243
  return;
2127
2244
  }
2128
2245
  CT.error={code:code,message:msg};
@@ -2166,8 +2283,8 @@
2166
2283
  },
2167
2284
  stalled:function(){
2168
2285
  eventTarget('stalled');//注册监听error
2169
- CT.error={code:13,message:'load:stalled'};
2170
- eventTarget('error',CT.error);//注册监听error
2286
+ // HLS 在切片预热、切换清晰度时会短暂触发 stalled,属于可恢复缓冲。
2287
+ // 不把它上报为致命 error,避免业务侧误判已失败而覆盖正常播放状态。
2171
2288
  },
2172
2289
  suspend:function(){
2173
2290
  eventTarget('suspend');//注册监听error
@@ -2180,9 +2297,7 @@
2180
2297
  seeked:function(){
2181
2298
  seekApplyLock=false;
2182
2299
  decodeRecoverCount=0;
2183
- if(paused){
2184
- player.play();
2185
- }
2300
+ startPendingAutoplay();
2186
2301
  pSliderMouseDown=false;
2187
2302
  eventTarget('seek',{time:this.currentTime,state:'seeked'});
2188
2303
  eventTarget('seeked');
@@ -3054,7 +3169,14 @@
3054
3169
  }
3055
3170
  else{
3056
3171
  if(loadedmetadataNum>0 && video){
3057
- try{video.play();}catch(event){console.error(event)}
3172
+ try{
3173
+ var playPromise=video.play();
3174
+ // play() 返回 Promise 时必须消费 rejection,避免 autoplay 策略或
3175
+ // 页面卸载造成未捕获异常;播放状态仍由原生事件同步。
3176
+ if(playPromise && typeof playPromise.catch==='function'){
3177
+ playPromise.catch(function(){});
3178
+ }
3179
+ }catch(event){}
3058
3180
  }
3059
3181
  }
3060
3182
  return player;
@@ -3242,6 +3364,17 @@
3242
3364
  applyPendingSeek(false);
3243
3365
  return player;
3244
3366
  }
3367
+ // HLS:无前序关键帧缓冲时禁止直接 startLoad(target)
3368
+ if(hls && target>1 && !hlsCanSafelySeekTo(target)){
3369
+ seekTime=target;
3370
+ hlsResumeWarmup=true;
3371
+ hlsResumeTarget=target;
3372
+ try{
3373
+ hls.startLoad(hlsPriorLoadTime(target));
3374
+ }catch(event){}
3375
+ applyPendingSeek(false);
3376
+ return player;
3377
+ }
3245
3378
  if(hlsSeekTo(target, false)){
3246
3379
  seekTime=0;
3247
3380
  return player;
@@ -7061,7 +7194,7 @@
7061
7194
  if(!isRetry && (!vars || vars['cdnFallback']!==false)){
7062
7195
  var cdn=(typeof window!=='undefined' && window.ckplayerPlusCdn) ? window.ckplayerPlusCdn : ckplayerPlusDefaultCdn;
7063
7196
  var m=String(file).match(/\/(hls\.js|flv\.js|mpegts\.js|language)\/([^/?#]+)(?:[?#].*)?$/i);
7064
- if(cdn && cdn.indexOf('1.1.0')===-1 && m){
7197
+ if(cdn && cdn.indexOf('1.1.1')===-1 && m){
7065
7198
  if(cdn.slice(-1)!=='/'){
7066
7199
  cdn+='/';
7067
7200
  }
@@ -7984,4 +8117,4 @@
7984
8117
  }
7985
8118
  }
7986
8119
  return ckplayerEmbed;
7987
- }));
8120
+ }));