tencent.jquery.pix.component 1.0.6-6.beta1

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.
@@ -0,0 +1,425 @@
1
+ import { $, windowEnv } from "../config";
2
+ import VideoHTML from "./videohtml";
3
+ import './videocss.scss'
4
+
5
+ /**
6
+ * @typedef {"play" | "pause" | "stop" | "enterFullScreen" | "exitFullScreen"} VideoPlayerState
7
+ * 视频播放器状态
8
+ */
9
+
10
+ /**
11
+ * 在页面上放置一个视频播放器组件(腾讯视频)
12
+ * @constructor
13
+ * @param {object} options 选项
14
+ * @param {string} options.container 容器元素的jquery选择器
15
+ * @param {string} options.vid 腾讯视频id
16
+ * @param {string} [options.previewUrl] 视频预览图
17
+ * @param {0|1} [options.videoType=1] 视频类型选项,0: 标清,1: 高清
18
+ * @param {number} [options.autoHideControlsDelayMs=5000] 自动隐藏控制条延迟时间,单位ms
19
+ * @param {boolean} [options.showProgressBar=true] 是否显示进度条
20
+ * @param {boolean} [options.clickToPause=false] 点击播放区域是否直接暂停,true: 直接暂停,false: 先展示控制条
21
+ * @param {(this: VideoPlayer, type: VideoPlayerState) => any} [options.stateChanged] 状态变化回调
22
+ */
23
+ export function VideoPlayer(options) {
24
+ this.options = options
25
+ if (typeof this.options.stateChanged !== 'function') {
26
+ this.options.stateChanged = () => {};
27
+ }
28
+ this.init()
29
+ }
30
+
31
+ VideoPlayer.prototype.init = async function() {
32
+ if (windowEnv === 'h5') {
33
+ this.durationFactor = 1; // 浏览器,单位s
34
+ } else {
35
+ this.durationFactor = 1000; // PixUI,单位ms
36
+ }
37
+
38
+ const $container = $(this.options.container);
39
+ this.$container = $container;
40
+ $container.html(VideoHTML);
41
+
42
+ if (this.options.previewUrl) {
43
+ $container.find('.myplayer-video-preview').css('background-image', `url(${this.options.previewUrl})`);
44
+ }
45
+
46
+ // 缺省选项
47
+ this.options = {
48
+ videoType: 1,
49
+ autoHideControlsDelayMs: 5000,
50
+ showProgressBar: true,
51
+ clickToPause: false,
52
+ ...this.options
53
+ }
54
+
55
+ const videoURL = await getVideoURL(this.options.vid, this.options.videoType);
56
+ $container.find('.myplayer-video-mask').before(`<video src="${videoURL}"></video>`);
57
+
58
+ this.state = {
59
+ playing: false,
60
+ controlsVisible: false,
61
+ updatingProgress: false,
62
+ controlsDelayStart: 0,
63
+ firstPlay: true,
64
+ }
65
+
66
+ // 根据showProgressBar选项控制进度条显示
67
+ if (!this.options.showProgressBar) {
68
+ $container.find('.myplayer-progress').hide();
69
+ }
70
+
71
+ this.bindEvent();
72
+ }
73
+
74
+ VideoPlayer.prototype.bindEvent = function() {
75
+ const container = this.$container;
76
+ container.off()
77
+ .on('click', '.myplayer-video-cover', () => {
78
+ this.play();
79
+ })
80
+ .on('click', '.myplayer-video-mask', () => {
81
+ this.maskClicked();
82
+ })
83
+ .on('click', '.myplayer-btn-play', () => {
84
+ this.play();
85
+ })
86
+ .on('click', '.myplayer-btn-pause', () => {
87
+ this.pause();
88
+ })
89
+ .on('click', '.myplayer-btn-full-screen', () => {
90
+ this.toggleFullScreen();
91
+ })
92
+ .on('click', '.myplayer-btn-exit-full-screen', () => {
93
+ this.toggleFullScreen();
94
+ })
95
+ .on('canplay', 'video', () => {
96
+ console.log('canplay');
97
+ this.updateTotalTime();
98
+ });
99
+
100
+ // 只有在显示进度条时才绑定进度条相关事件
101
+ if (this.options.showProgressBar) {
102
+ container
103
+ .on('dragstart', () => { console.log('video container dragstart') }, true)
104
+ .on('drag', () => { console.log('video container drag') }, true)
105
+ .on('dragend', () => { console.log('video container dragend') }, true)
106
+ .on('click', () => { console.log('video container click') }, true)
107
+ .on('dragstart', '.myplayer-progress', (e) => {
108
+ if (e.originalEvent) {
109
+ this.progressDragStart(e.originalEvent);
110
+ } else {
111
+ this.progressDragStart(e);
112
+ }
113
+ })
114
+ .on('drag', '.myplayer-progress', (e) => {
115
+ if (e.originalEvent) {
116
+ this.progressDrag(e.originalEvent);
117
+ } else {
118
+ this.progressDrag(e);
119
+ }
120
+ })
121
+ .on('dragend', '.myplayer-progress', (e) => {
122
+ if (e.originalEvent) {
123
+ this.progressDragEnd(e.originalEvent);
124
+ } else {
125
+ this.progressDragEnd(e);
126
+ }
127
+ })
128
+ .on('click', '.myplayer-progress', (e) => {
129
+ if (e.originalEvent) {
130
+ this.progressDragEnd(e.originalEvent);
131
+ } else {
132
+ this.progressDragEnd(e);
133
+ }
134
+ });
135
+ }
136
+ }
137
+
138
+ VideoPlayer.prototype.play = function() {
139
+ if (this.state.firstPlay) {
140
+ this.state.firstPlay = false;
141
+ this.$container.find('.myplayer-video-cover,.myplayer-video-preview').hide();
142
+ this.updateTotalTime();
143
+ }
144
+ const video = this.$container.find('video')[0];
145
+ video.play();
146
+ this.state.playing = true;
147
+ this.$container.find('.myplayer-btn-play').hide();
148
+ this.$container.find('.myplayer-btn-big-play').addClass('myplayer-transparent');
149
+ this.$container.find('.myplayer-btn-pause').show();
150
+ this.$container.find('.myplayer-progress').css('transform', 'none');
151
+ this.startUpdatingProgress();
152
+ this.hideControlsWithDelay();
153
+ this.options.stateChanged.call(this, 'play');
154
+ }
155
+
156
+ VideoPlayer.prototype.pause = function() {
157
+ this.showControls();
158
+ const video = this.$container.find('video')[0];
159
+ video.pause();
160
+ this.state.playing = false;
161
+ this.$container.find('.myplayer-btn-play').show();
162
+ this.$container.find('.myplayer-btn-big-play').removeClass('myplayer-transparent');
163
+ this.$container.find('.myplayer-btn-pause').hide();
164
+ this.$container.find('.myplayer-progress').css('transform', 'scaleY(2)');
165
+ this.state.controlsDelayStart = Date.now();
166
+ this.options.stateChanged.call(this, 'pause');
167
+ }
168
+
169
+ VideoPlayer.prototype.stop = function() {
170
+ const video = this.$container.find('video')[0];
171
+ video.pause();
172
+ video.currentTime = 0;
173
+ this.state.playing = false;
174
+ this.$container.find('.myplayer-btn-play').show();
175
+ this.$container.find('.myplayer-btn-big-play').removeClass('myplayer-transparent');
176
+ this.$container.find('.myplayer-btn-pause').hide();
177
+ this.$container.find('.myplayer-progress').css('transform', 'scaleY(2)');
178
+ this.state.controlsDelayStart = Date.now();
179
+ this.$container.find('.myplayer-video-cover,.myplayer-video-preview').show();
180
+ this.state.firstPlay = true;
181
+ this.options.stateChanged.call(this, 'stop');
182
+ }
183
+
184
+ VideoPlayer.prototype.maskClicked = function() {
185
+ console.log('maskClicked')
186
+ if (this.state.controlsVisible === true) {
187
+ if (this.state.playing === true) {
188
+ this.pause();
189
+ } else {
190
+ this.play();
191
+ }
192
+ } else {
193
+ if (this.options.clickToPause && this.state.playing === true) {
194
+ // 如果配置了点击直接暂停,且正在播放,则直接暂停
195
+ this.pause();
196
+ } else {
197
+ // 否则先展示控制条
198
+ this.showControls();
199
+ }
200
+ }
201
+ }
202
+
203
+ VideoPlayer.prototype.showControls = function() {
204
+ const container = this.$container;
205
+ container.find('.myplayer-video-mask').removeClass('myplayer-transparent');
206
+ container.find('.myplayer-top').removeClass('myplayer-transparent');
207
+ container.find('.myplayer-bottom').removeClass('myplayer-transparent');
208
+ this.state.controlsVisible = true;
209
+ this.state.updatingProgress = true;
210
+ this.updateTotalTime();
211
+ this.startUpdatingProgress();
212
+
213
+ this.hideControlsWithDelay();
214
+ }
215
+
216
+ VideoPlayer.prototype.hideControls = function() {
217
+ const container = this.$container;
218
+ container.find('.myplayer-video-mask').addClass('myplayer-transparent');
219
+ container.find('.myplayer-top').addClass('myplayer-transparent');
220
+ container.find('.myplayer-bottom').addClass('myplayer-transparent');
221
+ this.state.controlsVisible = false;
222
+ this.state.updatingProgress = false;
223
+ }
224
+
225
+ VideoPlayer.prototype.hideControlsWithDelay = function() {
226
+ this.state.controlsDelayStart = Date.now();
227
+ setTimeout(() => {
228
+ const nowTime = Date.now();
229
+ console.debug("nowTime - this.state.controlsDelayStart", nowTime - this.state.controlsDelayStart);
230
+
231
+ // 有时正常时间差也会小于设定的时间,所以判断时减去100ms
232
+ if (this.state.playing === true && nowTime - this.state.controlsDelayStart >= this.options.autoHideControlsDelayMs - 100) {
233
+ this.hideControls();
234
+ }
235
+ }, this.options.autoHideControlsDelayMs);
236
+ }
237
+
238
+ VideoPlayer.prototype.startUpdatingProgress = function() {
239
+ this.updateProgress();
240
+
241
+ if (this.state.updatingProgress) {
242
+ window.requestAnimationFrame(this.startUpdatingProgress.bind(this));
243
+ }
244
+ }
245
+
246
+ VideoPlayer.prototype.updateProgress = function() {
247
+ const video = this.$container.find('video')[0];
248
+ const currentTime = video.currentTime;
249
+ const duration = video.duration;
250
+ const progress = currentTime / duration;
251
+
252
+ const parentWidth = this.$container.find('.myplayer-progress').width();
253
+ this.$container.find('.myplayer-subprogress').width(`${progress * parentWidth}px`);
254
+
255
+ this.$container.find('.myplayer-playtime').html(formatTime(currentTime / this.durationFactor));
256
+ }
257
+
258
+ VideoPlayer.prototype.updateTotalTime = function() {
259
+ const video = this.$container.find('video')[0];
260
+ const duration = video.duration;
261
+
262
+ this.$container.find('.myplayer-totaltime').html(formatTime(duration / this.durationFactor));
263
+ }
264
+
265
+ VideoPlayer.prototype.toggleFullScreen = function() {
266
+ const $container = this.$container;
267
+ const fullScreenBtn = $container.find('.myplayer-btn-full-screen');
268
+ const exitFullScreenBtn = $container.find('.myplayer-btn-exit-full-screen');
269
+ const bottom = $container.find('.myplayer-bottom');
270
+ const innerContainer = $container.find('.myplayer-container');
271
+
272
+ if (innerContainer.hasClass('myplayer-full-screen')) {
273
+ innerContainer.removeClass('myplayer-full-screen');
274
+ bottom.removeClass('myplayer-full-screen');
275
+ fullScreenBtn.show();
276
+ exitFullScreenBtn.hide();
277
+ this.options.stateChanged.call(this, 'exitFullScreen');
278
+ } else {
279
+ innerContainer.addClass('myplayer-full-screen');
280
+ bottom.addClass('myplayer-full-screen');
281
+ fullScreenBtn.hide();
282
+ exitFullScreenBtn.show();
283
+ this.options.stateChanged.call(this, 'enterFullScreen');
284
+ }
285
+
286
+ this.hideControlsWithDelay();
287
+ }
288
+
289
+ VideoPlayer.prototype.setFullScreen = function (target = true) {
290
+ const $container = this.$container;
291
+ const fullScreenBtn = $container.find('.myplayer-btn-full-screen');
292
+ const exitFullScreenBtn = $container.find('.myplayer-btn-exit-full-screen');
293
+ const bottom = $container.find('.myplayer-bottom');
294
+ const innerContainer = $container.find('.myplayer-container');
295
+
296
+ if (!target && innerContainer.hasClass('myplayer-full-screen')) {
297
+ innerContainer.removeClass('myplayer-full-screen');
298
+ bottom.removeClass('myplayer-full-screen');
299
+ fullScreenBtn.show();
300
+ exitFullScreenBtn.hide();
301
+ this.options.stateChanged.call(this, 'exitFullScreen');
302
+ } else if (target && !innerContainer.hasClass('myplayer-full-screen')) {
303
+ innerContainer.addClass('myplayer-full-screen');
304
+ bottom.addClass('myplayer-full-screen');
305
+ fullScreenBtn.hide();
306
+ exitFullScreenBtn.show();
307
+ this.options.stateChanged.call(this, 'enterFullScreen');
308
+ }
309
+
310
+ this.hideControlsWithDelay();
311
+ }
312
+
313
+ /**
314
+ * 进度条拖动开始
315
+ * @param {MouseEvent} e
316
+ */
317
+ VideoPlayer.prototype.progressDragStart = function(e) {
318
+ if (!this.options.showProgressBar) {
319
+ return;
320
+ }
321
+
322
+ this.state.updatingProgress = false;
323
+ if (this.state.playing === true) {
324
+ this.pause();
325
+ }
326
+ }
327
+
328
+ /**
329
+ * 进度条拖动
330
+ * @param {MouseEvent} e
331
+ */
332
+ VideoPlayer.prototype.progressDrag = function(e) {
333
+ if (!this.options.showProgressBar) {
334
+ return;
335
+ }
336
+
337
+ const $container = this.$container;
338
+ const progress = $container.find('.myplayer-progress');
339
+ const subprogress = $container.find('.myplayer-subprogress');
340
+ const video = $container.find('video')[0];
341
+
342
+ const parentWidth = progress.width();
343
+ const curOffset = Math.max(Math.min(e.offsetX, parentWidth), 0);
344
+ subprogress.width(`${curOffset}px`);
345
+ const adjustedPlayTime = curOffset / parentWidth * video.duration;
346
+ $container.find('.myplayer-playtime').html(formatTime(adjustedPlayTime / this.durationFactor));
347
+ }
348
+
349
+ /**
350
+ * 进度条拖动结束
351
+ * @param {MouseEvent} e
352
+ */
353
+ VideoPlayer.prototype.progressDragEnd = function(e) {
354
+ if (!this.options.showProgressBar) {
355
+ return;
356
+ }
357
+
358
+ const $container = this.$container;
359
+ const video = $container.find('video')[0];
360
+ const progress = $container.find('.myplayer-progress');
361
+ const parentWidth = progress.width();
362
+ const curOffset = Math.max(Math.min(e.offsetX, parentWidth), 0);
363
+
364
+ // currentTime set 单位s, get 单位ms
365
+ video.currentTime = curOffset / progress.width() * video.duration / this.durationFactor;
366
+
367
+ this.play();
368
+
369
+ this.state.updatingProgress = true;
370
+ this.startUpdatingProgress();
371
+ }
372
+
373
+ /**
374
+ * 获取视频真实地址
375
+ * @param {string} vid 腾讯视频vid
376
+ * @param {0|1} [type] 类型,0: 标清,1: 高清
377
+ * @returns {Promise<string>} 真实视频地址
378
+ */
379
+ const getVideoURL = async function(vid, type = 1) {
380
+ const sApiUrl = `https://vv.video.qq.com/getinfo?vid=${vid}&platform=101001&charge=0&otype=json&t=${Date.now()}`;
381
+
382
+ try {
383
+ console.log('processing vid through sApiUrl: ', sApiUrl);
384
+ const res = await fetchData(sApiUrl);
385
+ console.log('222 processing vid through sApiUrl: ', sApiUrl);
386
+ const processedStr = res.replace('QZOutputJson=', '');
387
+ const jsonStr = processedStr.substring(0, processedStr.length - 1);
388
+ const resObj = JSON.parse(jsonStr);
389
+ const sRealUrl = `${resObj.vl.vi[0].ul.ui[type].url}${resObj.vl.vi[0].fn}?vkey=${resObj.vl.vi[0].fvkey}`;
390
+ console.log('腾讯视频真实地址: ', sRealUrl);
391
+ return sRealUrl;
392
+ } catch (err) {
393
+ console.error(err)
394
+ return '';
395
+ }
396
+ }
397
+
398
+ /**
399
+ * 异步get请求
400
+ * @param {string} url
401
+ * @returns {Promise<string>}
402
+ */
403
+ const fetchData = function(url) {
404
+ const xhr = new XMLHttpRequest();
405
+ xhr.open('GET', url);
406
+ xhr.send();
407
+
408
+ return new Promise((resolve, reject) => {
409
+ xhr.onreadystatechange = function () {
410
+ if (xhr.readyState === 4) {
411
+ if (xhr.status === 200) {
412
+ resolve(xhr.responseText); // 请求成功,返回响应内容
413
+ } else {
414
+ reject(new Error('请求失败')); // 请求失败,返回错误对象
415
+ }
416
+ }
417
+ };
418
+ });
419
+ }
420
+
421
+ const formatTime = function(secs) {
422
+ const minutes = Math.floor(secs / 60);
423
+ const seconds = Math.floor(secs % 60);
424
+ return `${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
425
+ }