forked from xxxily/h5player
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathh5player.js
1764 lines (1615 loc) · 52.8 KB
/
h5player.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// ==UserScript==
// @name HTML5播放器增强插件 - 修订版
// @namespace https://github.com/xxxily/h5player
// @homepage https://github.com/xxxily/h5player
// @version 2.5.1
// @description 对HTML5播放器的功能进行增强,支持所有使用H5进行视频播放的网站,快捷键仿照Potplayer的快捷键布局,实现调节亮度,饱和度,对比度,速度等功能。
// @author ankvps
// @match http://*/*
// @match https://*/*
// @run-at document-start
// @grant GM_addStyle
// ==/UserScript==
/* 元素全屏API,同时兼容网页全屏 */
class FullScreen {
constructor (dom, pageMode) {
this.dom = dom
// 默认全屏模式,如果传入pageMode则表示进行的是页面全屏操作
this.pageMode = pageMode || false
const fullPageStyle = `
._webfullscreen_ {
display: block !important;
position: fixed !important;
width: 100% !important;
height: 100% !important;
top: 0 !important;
left: 0 !important;
background: #000 !important;
}
._webfullscreen_zindex_ {
z-index: 999998 !important;
}
`
if (!window._hasInitFullPageStyle_) {
// eslint-disable-next-line no-undef
GM_addStyle(fullPageStyle)
window._hasInitFullPageStyle_ = true
}
window.addEventListener('keyup', (event) => {
const key = event.key.toLowerCase()
if (key === 'escape') {
this.exit()
}
}, true)
}
eachParentNode (dom, fn) {
let parent = dom.parentNode
while (parent && parent.classList) {
const isEnd = fn(parent, dom)
parent = parent.parentNode
if (isEnd) {
break
}
}
}
getContainer () {
const t = this
if (t._container_) return t._container_
const d = t.dom
const domBox = d.getBoundingClientRect()
let container = d
t.eachParentNode(d, function (parentNode) {
const parentBox = parentNode.getBoundingClientRect()
if (parentBox.width <= domBox.width && parentBox.height <= domBox.height) {
container = parentNode
} else {
return true
}
})
t._container_ = container
return container
}
isFull () {
return this.dom.classList.contains('_webfullscreen_')
}
isFullScreen () {
const d = document
return !!(d.fullscreen || d.webkitIsFullScreen || d.mozFullScreen ||
d.fullscreenElement || d.webkitFullscreenElement || d.mozFullScreenElement)
}
enterFullScreen () {
const c = this.getContainer()
const enterFn = c.requestFullscreen || c.webkitRequestFullScreen || c.mozRequestFullScreen || c.msRequestFullScreen
enterFn && enterFn.call(c)
}
enter () {
const t = this
if (t.isFull()) return
const container = t.getContainer()
let needSetIndex = false
if (t.dom === container) {
needSetIndex = true
}
this.eachParentNode(t.dom, function (parentNode) {
parentNode.classList.add('_webfullscreen_')
if (container === parentNode || needSetIndex) {
needSetIndex = true
parentNode.classList.add('_webfullscreen_zindex_')
}
})
t.dom.classList.add('_webfullscreen_')
const fullScreenMode = !t.pageMode
if (fullScreenMode) {
t.enterFullScreen()
}
}
exitFullScreen () {
const d = document
const exitFn = d.exitFullscreen || d.webkitExitFullscreen || d.mozCancelFullScreen || d.msExitFullscreen
exitFn && exitFn.call(d)
}
exit () {
const t = this
t.dom.classList.remove('_webfullscreen_')
this.eachParentNode(t.dom, function (parentNode) {
parentNode.classList.remove('_webfullscreen_')
parentNode.classList.remove('_webfullscreen_zindex_')
})
const fullScreenMode = !t.pageMode
if (fullScreenMode || t.isFullScreen()) {
t.exitFullScreen()
}
}
toggle () {
this.isFull() ? this.exit() : this.enter()
}
}
(function () {
/**
* 任务配置中心 Task Control Center
* 用于配置所有无法进行通用处理的任务,如不同网站的全屏方式不一样,必须调用网站本身的全屏逻辑,才能确保字幕、弹幕等正常工作
* */
const TCC = {
/**
* 配置示例
* 父级键名对应的是一级域名,
* 子级键名对应的相关功能名称,键值对应的该功能要触发的点击选择器或者要调用的相关函数
* 所有子级的键值都支持使用选择器触发或函数调用
* 配置了子级的则使用子级配置逻辑进行操作,否则使用默认逻辑
* 注意:include,exclude这两个子级键名除外,这两个是用来进行url范围匹配的
* */
'demo.demo': {
fullScreen: '.fullscreen-btn',
exitFullScreen: '.exit-fullscreen-btn',
webFullScreen: function () {},
exitWebFullScreen: '.exit-fullscreen-btn',
autoPlay: '.player-start-btn',
pause: '.player-pause',
play: '.player-play',
switchPlayStatus: '.player-play',
playbackRate: function () {},
currentTime: function () {},
addCurrentTime: '.add-currenttime',
subtractCurrentTime: '.subtract-currenttime',
// 自定义快捷键的执行方式,如果是组合键,必须是 ctrl-->shift-->alt 这样的顺序,没有可以忽略,键名必须全小写
shortcuts: {
/* 注册要执行自定义回调操作的快捷键 */
register: [
'ctrl+shift+alt+c',
'ctrl+shift+c',
'ctrl+alt+c',
'ctrl+c',
'c'
],
/* 自定义快捷键的回调操作 */
callback: function (h5Player, taskConf, data) {
const { event, player } = data
console.log(event, player)
}
},
/* 当前域名下需包含的路径信息,默认整个域名下所有路径可用 必须是正则 */
include: /^.*/,
/* 当前域名下需排除的路径信息,默认不排除任何路径 必须是正则 */
exclude: /\t/
},
'youtube.com': {
// 'webFullScreen': 'button.ytp-size-button',
fullScreen: 'button.ytp-fullscreen-button'
},
'netflix.com': {
fullScreen: 'button.button-nfplayerFullscreen',
addCurrentTime: 'button.button-nfplayerFastForward',
subtractCurrentTime: 'button.button-nfplayerBackTen'
},
'bilibili.com': {
fullScreen: '[data-text="进入全屏"]',
webFullScreen: '[data-text="网页全屏"]',
autoPlay: '.bilibili-player-video-btn-start',
switchPlayStatus: '.bilibili-player-video-btn-start'
},
'live.bilibili.com': {
fullScreen: '.bilibili-live-player-video-controller-fullscreen-btn button',
webFullScreen: '.bilibili-live-player-video-controller-web-fullscreen-btn button',
switchPlayStatus: '.bilibili-live-player-video-controller-start-btn button'
},
'iqiyi.com': {
fullScreen: '.iqp-btn-fullscreen',
webFullScreen: '.iqp-btn-webscreen',
init: function (h5Player, taskConf) {
// 隐藏水印
hideDom('.iqp-logo-box')
// 移除暂停广告
GM_addStyle(`
div[templatetype="common_pause"]{ display:none }
`)
}
},
'youku.com': {
fullScreen: '.control-fullscreen-icon',
init: function (h5Player, taskConf) {
// 隐藏水印
hideDom('.youku-layer-logo')
}
},
'ted.com': {
fullScreen: 'button.Fullscreen'
},
'v.qq.com': {
pause: '.container_inner .txp-shadow-mod]',
play: '.container_inner .txp-shadow-mod',
shortcuts: {
register: ['c', 'x', 'z'],
callback: function (h5Player, taskConf, data) {
const { event } = data
const key = event.key.toLowerCase()
const speedItems = document.querySelectorAll('.container_inner txpdiv[data-role="txp-button-speed-list"] .txp_menuitem')
/* 利用sessionStorage下的playbackRate进行设置 */
if (window.sessionStorage.playbackRate && /(c|x|z)/.test(key)) {
const curSpeed = Number(window.sessionStorage.playbackRate)
const perSpeed = curSpeed - 0.1 >= 0 ? curSpeed - 0.1 : 0.1
const nextSpeed = curSpeed + 0.1 <= 4 ? curSpeed + 0.1 : 4
let targetSpeed = curSpeed
switch (key) {
case 'z' :
targetSpeed = 1
break
case 'c' :
targetSpeed = nextSpeed
break
case 'x' :
targetSpeed = perSpeed
break
}
window.sessionStorage.playbackRate = targetSpeed
h5Player.setCurrentTime(0.1, true)
h5Player.setPlaybackRate(targetSpeed, true)
return true
}
/* 模拟点击触发 */
if (speedItems.length >= 3 && /(c|x|z)/.test(key)) {
let curIndex = 1
speedItems.forEach((item, index) => {
if (item.classList.contains('txp_current')) {
curIndex = index
}
})
const perIndex = curIndex - 1 >= 0 ? curIndex - 1 : 0
const nextIndex = curIndex + 1 < speedItems.length ? curIndex + 1 : speedItems.length - 1
let target = speedItems[1]
switch (key) {
case 'z' :
target = speedItems[1]
break
case 'c' :
target = speedItems[nextIndex]
break
case 'x' :
target = speedItems[perIndex]
break
}
target.click()
const speedNum = Number(target.innerHTML.replace('x'))
h5Player.setPlaybackRate(speedNum)
}
}
},
init: function (h5Player, taskConf) {
// 隐藏水印
hideDom('.txp-watermark')
}
},
'pan.baidu.com': {
fullScreen: function (h5Player, taskConf) {
h5Player.playerInstance.parentNode.querySelector('.vjs-fullscreen-control').click()
}
},
/**
* 获取域名 , 目前实现方式不好,需改造,对地区性域名(如com.cn)、三级及以上域名支持不好
* */
getDomain: function () {
const host = window.location.host
let domain = host
const tmpArr = host.split('.')
if (tmpArr.length > 2) {
tmpArr.shift()
domain = tmpArr.join('.')
}
return domain
},
/**
* 格式化配置任务
* @param isAll { boolean } -可选 默认只格式当前域名或host下的配置任务,传入true则将所有域名下的任务配置都进行格式化
*/
formatTCC: function (isAll) {
const t = this
const keys = Object.keys(t)
const domain = t.getDomain()
const host = window.location.host
function formatter (item) {
const defObj = {
include: /^.*/,
exclude: /\t/
}
item.include = item.include || defObj.include
item.exclude = item.exclude || defObj.exclude
return item
}
const result = {}
keys.forEach(function (key) {
let item = t[key]
if (isObj(item)) {
if (isAll) {
item = formatter(item)
result[key] = item
} else {
if (key === host || key === domain) {
item = formatter(item)
result[key] = item
}
}
}
})
return result
},
/* 判断所提供的配置任务是否适用于当前URL */
isMatch: function (taskConf) {
const url = window.location.href
let isMatch = false
if (taskConf.include.test(url)) {
isMatch = true
}
if (taskConf.exclude.test(url)) {
isMatch = false
}
return isMatch
},
/**
* 获取任务配置,只能获取到当前域名下的任务配置信息
* @param taskName {string} -可选 指定具体任务,默认返回所有类型的任务配置
*/
getTaskConfig: function () {
const t = this
if (!t._hasFormatTCC_) {
t.formatTCC()
t._hasFormatTCC_ = true
}
const domain = t.getDomain()
const taskConf = t[window.location.host] || t[domain]
if (taskConf && t.isMatch(taskConf)) {
return taskConf
}
return {}
},
/**
* 执行当前页面下的相应任务
* @param taskName {object|string} -必选,可直接传入任务配置对象,也可用是任务名称的字符串信息,自己去查找是否有任务需要执行
* @param data {object} -可选,传给回调函数的数据
*/
doTask: function (taskName, data) {
const t = this
let isDo = false
if (!taskName) return isDo
const taskConf = isObj(taskName) ? taskName : t.getTaskConfig()
if (!isObj(taskConf) || !taskConf[taskName]) return isDo
const task = taskConf[taskName]
const wrapDom = h5Player.getPlayerWrapDom()
if (taskName === 'shortcuts') {
if (isObj(task) && getType(task.callback) === 'function') {
task.callback(h5Player, taskConf, data)
isDo = true
}
} else if (getType(task) === 'function') {
task(h5Player, taskConf, data)
isDo = true
} else {
/* 触发选择器上的点击事件 */
if (wrapDom && wrapDom.querySelector(task)) {
// 在video的父元素里查找,是为了尽可能兼容多实例下的逻辑
wrapDom.querySelector(task).click()
isDo = true
} else if (document.querySelector(task)) {
document.querySelector(task).click()
isDo = true
}
}
return isDo
}
}
/**
* 元素监听器
* @param selector -必选
* @param fn -必选,元素存在时的回调
* @param shadowRoot -可选 指定监听某个shadowRoot下面的DOM元素
* 参考:https://javascript.ruanyifeng.com/dom/mutationobserver.html
*/
function ready (selector, fn, shadowRoot) {
const listeners = []
const win = window
const doc = shadowRoot || win.document
const MutationObserver = win.MutationObserver || win.WebKitMutationObserver
let observer
function $ready (selector, fn) {
// 储存选择器和回调函数
listeners.push({
selector: selector,
fn: fn
})
if (!observer) {
// 监听document变化
observer = new MutationObserver(check)
observer.observe(shadowRoot || doc.documentElement, {
childList: true,
subtree: true
})
}
// 检查该节点是否已经在DOM中
check()
}
function check () {
for (let i = 0; i < listeners.length; i++) {
var listener = listeners[i]
var elements = doc.querySelectorAll(listener.selector)
for (let j = 0; j < elements.length; j++) {
var element = elements[j]
if (!element._isMutationReady_) {
element._isMutationReady_ = true
listener.fn.call(element, element)
}
}
}
}
$ready(selector, fn)
}
function hideDom (selector, delay) {
setTimeout(function () {
const dom = document.querySelector(selector)
if (dom) {
dom.style.opacity = 0
}
}, delay || 1000 * 3)
}
/**
* 某些网页用了attachShadow closed mode,需要open才能获取video标签,例如百度云盘
* 解决参考:
* https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=zh-cn#closed
* https://stackoverflow.com/questions/54954383/override-element-prototype-attachshadow-using-chrome-extension
*/
function hackAttachShadow () {
if (window._hasHackAttachShadow_) return
try {
window._shadowDomList_ = []
window.Element.prototype._attachShadow = window.Element.prototype.attachShadow
window.Element.prototype.attachShadow = function () {
const arg = arguments
if (arg[0] && arg[0].mode) {
// 强制使用 open mode
arg[0].mode = 'open'
}
const shadowRoot = this._attachShadow.apply(this, arg)
// 存一份shadowDomList
window._shadowDomList_.push(shadowRoot)
// 在document下面添加 addShadowRoot 自定义事件
const shadowEvent = new window.CustomEvent('addShadowRoot', {
shadowRoot,
detail: {
shadowRoot,
message: 'addShadowRoot',
time: new Date()
},
bubbles: true,
cancelable: true
})
document.dispatchEvent(shadowEvent)
return shadowRoot
}
window._hasHackAttachShadow_ = true
} catch (e) {
console.error('hackAttachShadow error by h5player plug-in')
}
}
hackAttachShadow()
/* 事件侦听hack */
function hackEventListener () {
const EVENT = window.EventTarget.prototype
if (EVENT._addEventListener) return
EVENT._addEventListener = EVENT.addEventListener
EVENT._removeEventListener = EVENT.removeEventListener
window._listenerList_ = []
// hack addEventListener
EVENT.addEventListener = function () {
const arg = arguments
const type = arg[0]
const listener = arg[1]
this._addEventListener.apply(this, arg)
this._listeners = this._listeners || {}
this._listeners[type] = this._listeners[type] || []
const listenerObj = {
target: this,
type,
listener,
options: arg[2],
addTime: new Date().getTime()
}
window._listenerList_.push(listenerObj)
this._listeners[type].push(listenerObj)
}
// hack removeEventListener
EVENT.removeEventListener = function () {
const arg = arguments
const type = arg[0]
const listener = arg[1]
this._removeEventListener.apply(this, arg)
this._listeners = this._listeners || {}
this._listeners[type] = this._listeners[type] || []
const result = []
this._listeners[type].forEach(function (listenerObj) {
if (listenerObj.listener !== listener) {
result.push(listenerObj)
}
})
this._listeners[type] = result
}
}
hackEventListener()
const quickSort = function (arr) {
if (arr.length <= 1) { return arr }
var pivotIndex = Math.floor(arr.length / 2)
var pivot = arr.splice(pivotIndex, 1)[0]
var left = []
var right = []
for (var i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat([pivot], quickSort(right))
}
/**
* 向上查找操作
* @param dom {Element} -必选 初始dom元素
* @param fn {function} -必选 每一级ParentNode的回调操作
* 如果函数返回true则表示停止向上查找动作
*/
function eachParentNode (dom, fn) {
let parent = dom.parentNode
while (parent) {
const isEnd = fn(parent, dom)
parent = parent.parentNode
if (isEnd) {
break
}
}
}
/**
* 准确地获取对象的具体类型
* @param obj { all } -必选 要判断的对象
* @returns {*} 返回判断的具体类型
*/
function getType (obj) {
if (obj == null) {
return String(obj)
}
return typeof obj === 'object' || typeof obj === 'function'
? (obj.constructor && obj.constructor.name && obj.constructor.name.toLowerCase()) ||
/function\s(.+?)\(/.exec(obj.constructor)[1].toLowerCase()
: typeof obj
}
function isObj (obj) {
return getType(obj) === 'object'
}
/**
* 深度合并两个可枚举的对象
* @param objA {object} -必选 对象A
* @param objB {object} -必选 对象B
* @param concatArr {boolean} -可选 合并数组,默认遇到数组的时候,直接以另外一个数组替换当前数组,将此设置true则,遇到数组的时候一律合并,而不是直接替换
* @returns {*|void}
*/
function mergeObj (objA, objB, concatArr) {
function isObj (obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}
function isArr (arr) {
return Object.prototype.toString.call(arr) === '[object Array]'
}
if (!isObj(objA) || !isObj(objB)) return objA
function deepMerge (objA, objB) {
const keys = Object.keys(objB)
keys.forEach(function (key) {
const subItemA = objA[key]
const subItemB = objB[key]
if (typeof subItemA === 'undefined') {
objA[key] = subItemB
} else {
if (isObj(subItemA) && isObj(subItemB)) {
/* 进行深层合并 */
objA[key] = deepMerge(subItemA, subItemB)
} else {
if (concatArr && isArr(subItemA) && isArr(subItemB)) {
objA[key] = subItemA.concat(subItemB)
} else {
objA[key] = subItemB
}
}
}
})
return objA
}
return deepMerge(objA, objB)
}
/**
* 多对象深度合并,合并规则基于mergeObj,但不存在concatArr选项
* @returns {*}
*/
function merge () {
let result = arguments[0]
for (var i = 0; i < arguments.length; i++) {
if (i) {
result = mergeObj(result, arguments[i])
}
}
return result
}
/* ua信息伪装 */
function fakeUA (ua) {
Object.defineProperty(navigator, 'userAgent', {
value: ua,
writable: false,
configurable: false,
enumerable: true
})
}
/* ua信息来源:https://developers.whatismybrowser.com */
const userAgentMap = {
android: {
chrome: 'Mozilla/5.0 (Linux; Android 9; SM-G960F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.157 Mobile Safari/537.36',
firefox: 'Mozilla/5.0 (Android 7.0; Mobile; rv:57.0) Gecko/57.0 Firefox/57.0'
},
iPhone: {
safari: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Mobile/15E148 Safari/604.1',
chrome: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/74.0.3729.121 Mobile/15E148 Safari/605.1'
},
iPad: {
safari: 'Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Mobile/15E148 Safari/604.1',
chrome: 'Mozilla/5.0 (iPad; CPU OS 12_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/74.0.3729.155 Mobile/15E148 Safari/605.1'
}
}
const fakeConfig = {
// 'tv.cctv.com': userAgentMap.iPhone.chrome,
// 'v.qq.com': userAgentMap.iPad.chrome,
'open.163.com': userAgentMap.iPhone.chrome,
'm.open.163.com': userAgentMap.iPhone.chrome
}
function debugMsg () {
const arg = Array.from(arguments)
arg.unshift('h5player debug message :')
console.info.apply(console, arg)
}
const h5Player = {
/* 提示文本的字号 */
fontSize: 16,
enable: true,
globalMode: true,
playerInstance: null,
scale: 1,
translate: {
x: 0,
y: 0
},
playbackRate: 1,
/* 快进快退步长 */
skipStep: 5,
/* 获取当前播放器的实例 */
player: function () {
const t = this
return t.playerInstance || t.getPlayerList()[0]
},
/* 每个网页可能存在的多个video播放器 */
getPlayerList: function () {
const list = []
function findPlayer (context) {
context.querySelectorAll('video').forEach(function (player) {
list.push(player)
})
}
findPlayer(document)
// 被封装在 shadow dom 里面的video
if (window._shadowDomList_) {
window._shadowDomList_.forEach(function (shadowRoot) {
findPlayer(shadowRoot)
})
}
return list
},
getPlayerWrapDom: function () {
const t = this
const player = t.player()
if (!player) return
let wrapDom = null
const playerBox = player.getBoundingClientRect()
eachParentNode(player, function (parent) {
if (parent === document || !parent.getBoundingClientRect) return
const parentBox = parent.getBoundingClientRect()
if (parentBox.width && parentBox.height) {
if (parentBox.width === playerBox.width && parentBox.height === playerBox.height) {
wrapDom = parent
}
}
})
return wrapDom
},
/**
* 初始化播放器实例
* @param isSingle 是否为单实例video标签
*/
initPlayerInstance: function (isSingle) {
const t = this
if (!t.playerInstance) return
const player = t.playerInstance
t.filter.reset()
t.initTips()
t.initPlaybackRate()
t.isFoucs()
/* 增加通用全屏,网页全屏api */
player._fullScreen_ = new FullScreen(player)
player._fullPageScreen_ = new FullScreen(player, true)
if (!player._hasCanplayEvent_) {
player.addEventListener('canplay', function (event) {
t.initAutoPlay(player)
})
player._hasCanplayEvent_ = true
}
/* 播放的时候进行相关同步操作 */
if (!player._hasPlayingInitEvent_) {
let setPlaybackRateOnPlayingCount = 0
player.addEventListener('playing', function (event) {
if (setPlaybackRateOnPlayingCount === 0) {
/* 同步之前设定的播放速度 */
t.setPlaybackRate()
if (isSingle === true) {
/* 恢复播放进度和进行进度记录 */
t.setPlayProgress(player)
setTimeout(function () {
t.playProgressRecorder(player)
}, 1000 * 3)
}
} else {
t.setPlaybackRate(null, true)
}
setPlaybackRateOnPlayingCount += 1
})
player._hasPlayingInitEvent_ = true
}
/* 进行自定义初始化操作 */
const taskConf = TCC.getTaskConfig()
if (taskConf.init) {
TCC.doTask('init', player)
}
},
initPlaybackRate: function () {
const t = this
t.playbackRate = t.getPlaybackRate()
},
getPlaybackRate: function () {
const t = this
const playbackRate = window.localStorage.getItem('_h5_player_playback_rate_') || t.playbackRate
return Number(Number(playbackRate).toFixed(1))
},
/* 设置播放速度 */
setPlaybackRate: function (num, notips) {
const taskConf = TCC.getTaskConfig()
if (taskConf.playbackRate) {
TCC.doTask('playbackRate')
return
}
const t = this
const player = t.player()
let curPlaybackRate
if (num) {
num = Number(num)
if (Number.isNaN(num)) {
console.error('h5player: 播放速度转换出错')
return false
}
if (num <= 0) {
num = 0.1
}
num = Number(num.toFixed(1))
curPlaybackRate = num
} else {
curPlaybackRate = t.getPlaybackRate()
}
/* 记录播放速度的信息 */
window.localStorage.setItem('_h5_player_playback_rate_', curPlaybackRate)
t.playbackRate = curPlaybackRate
player.playbackRate = curPlaybackRate
/* 本身处于1被播放速度的时候不再提示 */
if (!num && curPlaybackRate === 1) return
!notips && t.tips('播放速度:' + player.playbackRate + '倍')
},
/**
* 初始化自动播放逻辑
* 必须是配置了自动播放按钮选择器得的才会进行自动播放
*/
initAutoPlay: function (p) {
const t = this
const player = p || t.player()
// 在轮询重试的时候,如果实例变了,或处于隐藏页面中则不进行自动播放操作
if (!player || (p && p !== t.player()) || document.hidden) return
const taskConf = TCC.getTaskConfig()
if (player && taskConf.autoPlay && player.paused) {
TCC.doTask('autoPlay')
if (player.paused) {
// 轮询重试
if (!player._initAutoPlayCount_) {
player._initAutoPlayCount_ = 1
}
player._initAutoPlayCount_ += 1
if (player._initAutoPlayCount_ >= 10) {
return false
}
setTimeout(function () {
t.initAutoPlay(player)
}, 200)
}
}
},
setWebFullScreen: function () {
const t = this
const player = t.player()
const isDo = TCC.doTask('webFullScreen')
if (!isDo && player && player._fullPageScreen_) {
player._fullPageScreen_.toggle()
}
},
setCurrentTime: function (num, notips) {
if (!num) return
num = Number(num)
const _num = Math.abs(Number(num.toFixed(1)))
const t = this
const player = t.player()
const taskConf = TCC.getTaskConfig()
if (taskConf.currentTime) {
TCC.doTask('currentTime')
return
}
if (num > 0) {
if (taskConf.addCurrentTime) {
TCC.doTask('addCurrentTime')
} else {
player.currentTime += _num
!notips && t.tips('前进:' + _num + '秒')
}
} else {
if (taskConf.subtractCurrentTime) {
TCC.doTask('subtractCurrentTime')
} else {
player.currentTime -= _num
!notips && t.tips('后退:' + _num + '秒')
}
}
},
setVolume: function (num) {
if (!num) return
num = Number(num)
const _num = Math.abs(Number(num.toFixed(2)))
const t = this
const player = t.player()
if (num > 0) {
if (player.volume < 1) {
player.volume += _num
}
} else {
if (player.volume > 0) {
player.volume -= _num
}
}
t.tips('音量:' + parseInt(player.volume * 100) + '%')
},
setFakeUA (ua) {
ua = ua || userAgentMap.iPhone.safari
/* 记录设定的ua信息 */
window.localStorage.setItem('_h5_player_user_agent_', ua)
fakeUA(ua)
},
/* ua伪装切换开关 */
switchFakeUA (ua) {
const customUA = window.localStorage.getItem('_h5_player_user_agent_')
if (customUA) {
window.localStorage.removeItem('_h5_player_user_agent_')
} else {
this.setFakeUA(ua)
}
debugMsg('ua', navigator.userAgent)
},
switchPlayStatus: function () {
const t = this
const player = t.player()
const taskConf = TCC.getTaskConfig()
if (taskConf.switchPlayStatus) {
TCC.doTask('switchPlayStatus')
return
}
if (player.paused) {
if (taskConf.play) {
TCC.doTask('play')
} else {
player.play()
t.tips('播放')
}
} else {
if (taskConf.pause) {
TCC.doTask('pause')
} else {
player.pause()
t.tips('暂停')
}
}