ckplayer-plus 1.1.1 → 1.1.3

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/README.md CHANGED
@@ -63,7 +63,7 @@ React:
63
63
  | `container` | 核心 API 必须;组件不用 | — | 播放器挂载节点。Vue/React 组件内部自动创建,**不要传** |
64
64
  | `video` | 必须 | — | 视频地址或清晰度数组 |
65
65
  | `poster` | 可选 | `''` | 封面图 |
66
- | `autoplay` | 可选 | `false` | 自动播放(移动端常需静音) |
66
+ | `autoplay` | 可选 | `false` | 自动播放。**课程/断点续播请保持 `false`**:有 `seek` 时组件会强制关闭自动播,只定位进度,需用户点击播放 |
67
67
  | `volume` | 可选 | `0.8` | 音量 0–1 |
68
68
  | `live` | 可选 | `false` | 直播相关 |
69
69
  | `width` / `height` | 组件可选 | `100%` / `400px` | 仅 Vue/React 组件样式 |
@@ -77,7 +77,7 @@ React:
77
77
  | `preferBuiltinPlug` | `true` | 优先加载本包内置脚本,避免复用页面上其它全局 `Hls` |
78
78
  | `cdnFallback` | `true` | `getPath` 失败时回退 jsDelivr;内网无外网请设 `false` 并配 `pluginPath` |
79
79
  | `pluginPath` | 自动 | 插件脚本根路径。默认:`window.ckplayerBasePath` → 本包 `dist/` → jsDelivr CDN |
80
- | `seek` | `0` | 续播秒数(number 或数字字符串);会同步 `maxSeeTime`。HLS 优先用 `startPosition`,并等待缓冲后再跳,降低非关键帧解码失败 |
80
+ | `seek` | `0` | 续播秒数。HLS 对齐分片起点缓冲后再定位;**只 seek 不自动 play**,避免 Network 出现 canceled `.ts` |
81
81
  | `loaded` | — | **只支持函数**;字符串形式已禁用(防 XSS) |
82
82
 
83
83
  ### 进阶(按需)
@@ -125,7 +125,7 @@ export default {
125
125
  </script>
126
126
  ```
127
127
 
128
- 断点续播:`seek` 用 `:seek` 或 `options.seek` 在创建时传入;**不要**在 `@ready` 里再 `player.seek(16)`,否则会重复加载 HLS、取消 `.ts` 分片导致卡住。
128
+ 断点续播:`seek` 用 `:seek` 或 `options.seek` 在创建时传入;**不要**在 `@ready` 里再 `player.seek(16)`,否则会重复加载 HLS、取消 `.ts` 分片导致卡住。有 `seek` 时不会自动播放,定位完成后点中央播放按钮即可。切集/切换清晰度同样不强制自动播。
129
129
 
130
130
  SSR(Nuxt 等)请用 `<ClientOnly>`,仅在浏览器初始化。
131
131
 
package/dist/ckplayer.js CHANGED
@@ -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.1/dist/';
144
+ var ckplayerPlusDefaultCdn='https://cdn.jsdelivr.net/npm/ckplayer-plus@1.1.3/dist/';
145
145
  function ckplayerEmbed(videoObj){
146
146
  /*
147
147
  * rightMenu
@@ -446,7 +446,7 @@
446
446
  }
447
447
  var allowCdn=!vars || vars['cdnFallback']!==false;
448
448
  var cdn=(typeof window!=='undefined' && window.ckplayerPlusCdn) ? window.ckplayerPlusCdn : ckplayerPlusDefaultCdn;
449
- if(!allowCdn || !cdn || cdn.indexOf('1.1.1')>-1){
449
+ if(!allowCdn || !cdn || cdn.indexOf('1.1.3')>-1){
450
450
  // 避免占位版本或显式关闭 CDN 时请求坏地址;交给相对路径或业务 pluginPath
451
451
  return folder+'/'+file;
452
452
  }
@@ -594,7 +594,13 @@
594
594
  // 不使用原生 autoplay 属性:浏览器会在 seek 前开始 play,随后
595
595
  // currentTime / pause 等初始化动作会打断其 Promise,导致永久 loading。
596
596
  video.attr('preload','auto');
597
- autoplayPending=true;
597
+ // 带断点续播时不自动播:只定位进度,等用户点击(避免 .ts canceled)
598
+ if(parseResumeSeekFromVars()>0){
599
+ autoplayPending=false;
600
+ }
601
+ else{
602
+ autoplayPending=true;
603
+ }
598
604
  }
599
605
  else{
600
606
  video.attr('preload','metadata');
@@ -763,6 +769,10 @@
763
769
  if(!seekTime){
764
770
  applyResumeSeekState(parseResumeSeekFromVars());
765
771
  }
772
+ // 有续播目标:取消自动播放,定位后展示中央播放按钮
773
+ if(seekTime>0){
774
+ autoplayPending=false;
775
+ }
766
776
  },
767
777
  /*
768
778
  * hlsSeekTo
@@ -990,10 +1000,8 @@
990
1000
  if(!resumeAt){
991
1001
  resumeAt=parseResumeSeekFromVars();
992
1002
  }
993
- // 部分旧 HLS 的分片不从关键帧开始。直接以续播点 startPosition / startLoad(target)
994
- // 起播会触发 Chrome PIPELINE_ERROR_DECODE。策略:
995
- // 1) 从目标前约一个分片开始加载,让前序关键帧进入缓冲
996
- // 2) 等 hlsCanSafelySeekTo 后再写 currentTime
1003
+ // HLS 续播:不要用精确秒数做 startPosition(易在分片中部起播导致解码失败/永久 loading)。
1004
+ // 对齐目标分片起点加载,等分片从起点缓冲到目标后再一次 currentTime,并强制 play。
997
1005
  if(resumeAt && resumeAt>0 && (isUndefined(vars['live']) || !vars['live'])){
998
1006
  maxSeeTime=Math.max(maxSeeTime,resumeAt);
999
1007
  if(firstSeekTime<0){
@@ -1002,16 +1010,28 @@
1002
1010
  seekTime=resumeAt;
1003
1011
  hlsResumeTarget=resumeAt;
1004
1012
  hlsResumeWarmup=true;
1005
- hlsConf.startPosition=hlsPriorLoadTime(resumeAt);
1006
1013
  }
1007
1014
  hls = new Hls(hlsConf);
1008
1015
  hls.loadSource(url);
1009
1016
  hls.attachMedia(video);
1010
1017
  hls.on(Hls.Events.MANIFEST_PARSED, function(){
1011
1018
  if(hlsResumeWarmup && seekTime>0){
1019
+ var loadAt=hlsPriorLoadTime(seekTime);
1012
1020
  try{
1013
- hls.startLoad(hlsPriorLoadTime(seekTime));
1021
+ hls.startLoad(loadAt);
1014
1022
  }catch(event){}
1023
+ // 超时兜底:仍未定位则对齐分片起点起播,避免一直转圈
1024
+ setTimeout(function(){
1025
+ if(!hlsResumeWarmup || !seekTime){return;}
1026
+ var target=seekTime;
1027
+ if(hlsCanSafelySeekTo(target)){
1028
+ finishResumeSeek(target);
1029
+ return;
1030
+ }
1031
+ var snap=hlsPriorLoadTime(target);
1032
+ hlsResumeWarmup=false;
1033
+ finishResumeSeek(snap);
1034
+ },5000);
1015
1035
  return;
1016
1036
  }
1017
1037
  if(!hlsStartPositionUsed){
@@ -1020,10 +1040,8 @@
1020
1040
  });
1021
1041
  hls.on(Hls.Events.FRAG_BUFFERED, function(event, data){
1022
1042
  if(hlsResumeWarmup && seekTime>0){
1023
- // 必须等「目标 + 前序分片」都在同一连续缓冲里,再定位
1024
1043
  if(hlsCanSafelySeekTo(seekTime)){
1025
- hlsResumeWarmup=false;
1026
- applySeekToMedia(seekTime);
1044
+ finishResumeSeek(seekTime);
1027
1045
  }
1028
1046
  return;
1029
1047
  }
@@ -1033,7 +1051,6 @@
1033
1051
  var fragStart=data.frag.start;
1034
1052
  var fragEnd=fragStart+data.frag.duration;
1035
1053
  if(hlsResumeTarget>=fragStart-0.5 && hlsResumeTarget<=fragEnd+0.5){
1036
- // 续播分片已进入缓冲,解除 waiting
1037
1054
  if(!waited){
1038
1055
  waited=true;
1039
1056
  if(C['buffer']){
@@ -1833,38 +1850,114 @@
1833
1850
  }catch(event){}
1834
1851
  return false;
1835
1852
  },
1853
+ /*
1854
+ * hlsFragAround
1855
+ * 功能:查找覆盖指定时间的 HLS 分片(用于对齐关键帧起点)
1856
+ */
1857
+ hlsFragAround=function(t){
1858
+ if(!hls || isNaN(t) || t<0){
1859
+ return null;
1860
+ }
1861
+ try{
1862
+ var level=-1;
1863
+ if(typeof hls.currentLevel==='number' && hls.currentLevel>=0){
1864
+ level=hls.currentLevel;
1865
+ }
1866
+ else if(typeof hls.firstLevel==='number' && hls.firstLevel>=0){
1867
+ level=hls.firstLevel;
1868
+ }
1869
+ else{
1870
+ level=0;
1871
+ }
1872
+ var details=hls.levels && hls.levels[level] && hls.levels[level].details;
1873
+ var frags=details && details.fragments;
1874
+ if(!frags || !frags.length){
1875
+ return null;
1876
+ }
1877
+ for(var i=0;i<frags.length;i++){
1878
+ var f=frags[i];
1879
+ if(!f){continue;}
1880
+ var start=f.start||0;
1881
+ var end=start+(f.duration||0);
1882
+ if(t>=start-0.05 && t<end+0.05){
1883
+ return f;
1884
+ }
1885
+ }
1886
+ // 超出末片时回退最后一片
1887
+ return frags[frags.length-1];
1888
+ }catch(event){}
1889
+ return null;
1890
+ },
1836
1891
  /*
1837
1892
  * hlsPriorLoadTime
1838
- * 功能:续播加载起点——至少回退约一个分片,保证目标分片前仍有关键帧可参考
1893
+ * 功能:续播加载起点——优先对齐目标分片起点(该流分片开头通常有 IDR)
1839
1894
  */
1840
1895
  hlsPriorLoadTime=function(t){
1841
1896
  if(isNaN(t) || t<=0){
1842
1897
  return 0;
1843
1898
  }
1899
+ var frag=hlsFragAround(t);
1900
+ if(frag && !isUndefined(frag.start)){
1901
+ return Math.max(0,frag.start);
1902
+ }
1844
1903
  return Math.max(0,t-12);
1845
1904
  },
1846
1905
  /*
1847
1906
  * hlsCanSafelySeekTo
1848
- * 功能:目标已缓冲,且同一连续缓冲区间覆盖目标前约一个分片
1849
- * 部分旧 TS 分片首帧不是关键帧;若只缓冲目标分片就 seek,Chrome 会报 PIPELINE_ERROR_DECODE
1907
+ * 功能:目标分片已从分片起点连续缓冲到目标点,才允许 currentTime
1908
+ * 避免分片未完整进入 MSE 时中途 seek 触发 PIPELINE_ERROR_DECODE / 永久 loading
1850
1909
  */
1851
1910
  hlsCanSafelySeekTo=function(t){
1852
1911
  if(!video || isUndefined(video.buffered) || isNaN(t) || t<0){
1853
1912
  return false;
1854
1913
  }
1914
+ if(!timeInBuffered(t)){
1915
+ return false;
1916
+ }
1917
+ var fragStart=hlsPriorLoadTime(t);
1918
+ // 同一连续区间需覆盖分片起点→目标
1855
1919
  try{
1856
1920
  var buf=video.buffered;
1857
- var needFrom=hlsPriorLoadTime(t);
1858
1921
  for(var i=0;i<buf.length;i++){
1859
1922
  var start=buf.start(i);
1860
1923
  var end=buf.end(i);
1861
- if(t<=end+0.15 && needFrom>=start-0.35){
1924
+ if(fragStart>=start-0.35 && t<=end+0.15){
1862
1925
  return true;
1863
1926
  }
1864
1927
  }
1865
1928
  }catch(event){}
1866
1929
  return false;
1867
1930
  },
1931
+ /*
1932
+ * finishResumeSeek
1933
+ * 功能:完成续播定位。只 seek、不自动 play(由用户点击中央按钮/播放键)
1934
+ * 避免 seek 后立刻 play 打断 HLS 分片请求(Network 出现 canceled .ts)并导致永久转圈
1935
+ */
1936
+ finishResumeSeek=function(t){
1937
+ hlsResumeWarmup=false;
1938
+ // 续播场景取消挂起的自动播放,统一改为点击播放
1939
+ autoplayPending=false;
1940
+ applySeekToMedia(t);
1941
+ setTimeout(function(){
1942
+ if(!video){return;}
1943
+ try{
1944
+ video.pause();
1945
+ }catch(event){}
1946
+ paused=true;
1947
+ if(C['buffer']){
1948
+ C['buffer'].hide();
1949
+ }
1950
+ waited=true;
1951
+ if(!vars['controls'] && C['centerPlay']){
1952
+ C['centerPlay'].show();
1953
+ }
1954
+ if(C['bar'] && C['bar']['playAndPause']){
1955
+ C['bar']['playAndPause']['pause'].hide();
1956
+ C['bar']['playAndPause']['play'].show();
1957
+ C['bar']['playAndPause']['refresh'].hide();
1958
+ }
1959
+ },120);
1960
+ },
1868
1961
  /*
1869
1962
  * clearSeekRetry
1870
1963
  * 功能:清理续播等待计时器
@@ -1933,17 +2026,14 @@
1933
2026
  firstSeekTime=t;
1934
2027
  }
1935
2028
  }
1936
- // HLS 首分片尚未完成预热时不能直接 startLoad(target),否则某些
1937
- // 非关键帧分片会让浏览器解码器永久报错。
2029
+ // HLS 首分片尚未完成预热时不能直接 startLoad(target)
1938
2030
  if(hlsResumeWarmup){
1939
2031
  if(hlsCanSafelySeekTo(t)){
1940
- hlsResumeWarmup=false;
1941
- applySeekToMedia(t);
2032
+ finishResumeSeek(t);
1942
2033
  }
1943
2034
  return;
1944
2035
  }
1945
- // HLS:用 hls.startLoad 从目标分片加载(去重,避免取消进行中的 .ts)
1946
- // 若目标附近尚无前序缓冲,改为预热模式从更早处分片加载
2036
+ // HLS:用 hls.startLoad 从目标分片起点加载(去重,避免取消进行中的 .ts)
1947
2037
  if(hls && typeof hls.startLoad==='function'){
1948
2038
  if(!hlsCanSafelySeekTo(t) && t>1){
1949
2039
  hlsResumeWarmup=true;
@@ -1998,8 +2088,11 @@
1998
2088
  if(isNaN(resumeAt) || resumeAt<0){
1999
2089
  resumeAt=0;
2000
2090
  }
2001
- // 每次失败再往前多退一个分片,避免反复落到同一坏点
2002
- var loadFrom=Math.max(0,resumeAt-(12*decodeRecoverCount));
2091
+ // 优先对齐分片起点(有 IDR);多次失败再往前一片
2092
+ var loadFrom=hlsPriorLoadTime(resumeAt);
2093
+ if(decodeRecoverCount>=2){
2094
+ loadFrom=Math.max(0,loadFrom-10);
2095
+ }
2003
2096
  seekApplyLock=false;
2004
2097
  clearSeekRetry();
2005
2098
  try{
@@ -2008,7 +2101,9 @@
2008
2101
  }
2009
2102
  }catch(event){}
2010
2103
  if(resumeAt>0 && hls && typeof hls.startLoad==='function'){
2011
- seekTime=resumeAt;
2104
+ // 第 3 次直接落到分片起点起播,保证能出画面
2105
+ var target=(decodeRecoverCount>=3)?loadFrom:resumeAt;
2106
+ seekTime=target;
2012
2107
  hlsResumeTarget=resumeAt;
2013
2108
  hlsResumeWarmup=true;
2014
2109
  hlsLoadAt=-1;
@@ -2017,31 +2112,20 @@
2017
2112
  hls.startLoad(loadFrom);
2018
2113
  }catch(event){}
2019
2114
  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
- }
2115
+ if(hlsCanSafelySeekTo(target)){
2116
+ finishResumeSeek(target);
2117
+ }
2118
+ else if(decodeRecoverCount>=2){
2119
+ finishResumeSeek(loadFrom);
2026
2120
  }
2027
2121
  else{
2028
2122
  applyPendingSeek(false);
2029
2123
  }
2030
- },500);
2124
+ },600);
2031
2125
  return true;
2032
2126
  }
2033
2127
  try{
2034
- var soft=Math.max(0,resumeAt-1.5);
2035
- seekTime=resumeAt;
2036
- if(video){
2037
- video.currentTime=soft;
2038
- }
2039
- setTimeout(function(){
2040
- applyPendingSeek(true);
2041
- if(video && video.paused && vars['autoplay']){
2042
- try{video.play();}catch(e2){}
2043
- }
2044
- },400);
2128
+ finishResumeSeek(Math.max(0,resumeAt-1.5));
2045
2129
  return true;
2046
2130
  }catch(event){}
2047
2131
  return false;
@@ -2303,7 +2387,8 @@
2303
2387
  eventTarget('seeked');
2304
2388
  },
2305
2389
  seeking:function(){
2306
- if(paused){
2390
+ // 续播/初始化定位中禁止抢先 play:会取消进行中的 .ts 并导致永久 loading
2391
+ if(paused && !seekApplyLock && !hlsResumeWarmup && !seekTime && !autoplayPending){
2307
2392
  player.play();
2308
2393
  }
2309
2394
  var seekingTime=this.currentTime;
@@ -4763,7 +4848,10 @@
4763
4848
  }
4764
4849
  }
4765
4850
  seekTime=player.time();
4766
- video.attr('autoplay','autoplay');
4851
+ // 切换清晰度只保留进度,不强制 autoplay(避免分片 canceled + 未经点击就播放)
4852
+ try{
4853
+ video.removeAttr('autoplay');
4854
+ }catch(event){}
4767
4855
  changeVideo(ele.attr('data-video'));
4768
4856
  message(language['switchTo']+ele.htm());
4769
4857
  C['bar']['definition']['button'].htm(ele.htm());
@@ -7194,7 +7282,7 @@
7194
7282
  if(!isRetry && (!vars || vars['cdnFallback']!==false)){
7195
7283
  var cdn=(typeof window!=='undefined' && window.ckplayerPlusCdn) ? window.ckplayerPlusCdn : ckplayerPlusDefaultCdn;
7196
7284
  var m=String(file).match(/\/(hls\.js|flv\.js|mpegts\.js|language)\/([^/?#]+)(?:[?#].*)?$/i);
7197
- if(cdn && cdn.indexOf('1.1.1')===-1 && m){
7285
+ if(cdn && cdn.indexOf('1.1.3')===-1 && m){
7198
7286
  if(cdn.slice(-1)!=='/'){
7199
7287
  cdn+='/';
7200
7288
  }