myetv-player 1.0.0 → 1.0.6
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/.github/workflows/codeql.yml +100 -0
- package/README.md +36 -58
- package/SECURITY.md +50 -0
- package/css/myetv-player.css +301 -218
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +1713 -1503
- package/dist/myetv-player.min.js +1670 -1471
- package/package.json +6 -1
- package/plugins/README.md +1016 -0
- package/plugins/cloudflare/README.md +1068 -0
- package/plugins/cloudflare/myetv-player-cloudflare-stream-plugin.js +556 -0
- package/plugins/facebook/README.md +1024 -0
- package/plugins/facebook/myetv-player-facebook-plugin.js +437 -0
- package/plugins/gamepad-remote-controller/README.md +816 -0
- package/plugins/gamepad-remote-controller/myetv-player-gamepad-remote-plugin.js +678 -0
- package/plugins/google-adsense-ads/README.md +1 -0
- package/plugins/google-adsense-ads/g-adsense-ads-plugin.js +158 -0
- package/plugins/google-ima-ads/README.md +1 -0
- package/plugins/google-ima-ads/g-ima-ads-plugin.js +355 -0
- package/plugins/twitch/README.md +1185 -0
- package/plugins/twitch/myetv-player-twitch-plugin.js +569 -0
- package/plugins/vast-vpaid-ads/README.md +1 -0
- package/plugins/vast-vpaid-ads/vast-vpaid-ads-plugin.js +346 -0
- package/plugins/vimeo/README.md +1416 -0
- package/plugins/vimeo/myetv-player-vimeo.js +640 -0
- package/plugins/youtube/README.md +851 -0
- package/plugins/youtube/myetv-player-youtube-plugin.js +1714 -210
- package/scss/README.md +160 -0
- package/scss/_menus.scss +840 -672
- package/scss/_responsive.scss +67 -105
- package/scss/_volume.scss +67 -105
- package/src/README.md +559 -0
- package/src/controls.js +16 -4
- package/src/core.js +1192 -1062
- package/src/i18n.js +27 -1
- package/src/quality.js +478 -436
- package/src/subtitles.js +2 -2
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MYETV Player - Twitch Plugin
|
|
3
|
+
* File: myetv-player-twitch-plugin.js
|
|
4
|
+
* Integrates Twitch live streams and VODs with full API control
|
|
5
|
+
* Created by https://www.myetv.tv https://oskarcosimo.com
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
(function () {
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
class TwitchPlugin {
|
|
12
|
+
constructor(player, options = {}) {
|
|
13
|
+
this.player = player;
|
|
14
|
+
this.options = {
|
|
15
|
+
// Video source (channel for live, video for VOD, collection for collections)
|
|
16
|
+
channel: options.channel || null,
|
|
17
|
+
video: options.video || null,
|
|
18
|
+
collection: options.collection || null,
|
|
19
|
+
|
|
20
|
+
// Parent domains (REQUIRED for embedding)
|
|
21
|
+
parent: options.parent || [window.location.hostname],
|
|
22
|
+
|
|
23
|
+
// Player dimensions
|
|
24
|
+
width: options.width || '100%',
|
|
25
|
+
height: options.height || '100%',
|
|
26
|
+
|
|
27
|
+
// Playback options
|
|
28
|
+
autoplay: options.autoplay !== false,
|
|
29
|
+
muted: options.muted || false,
|
|
30
|
+
time: options.time || '0h0m0s', // Start time for VODs (e.g., '1h30m45s')
|
|
31
|
+
|
|
32
|
+
// UI options
|
|
33
|
+
allowfullscreen: options.allowfullscreen !== false,
|
|
34
|
+
|
|
35
|
+
// Plugin options
|
|
36
|
+
debug: options.debug || false,
|
|
37
|
+
replaceNativePlayer: options.replaceNativePlayer !== false,
|
|
38
|
+
autoLoadFromData: options.autoLoadFromData !== false,
|
|
39
|
+
|
|
40
|
+
...options
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
this.twitchPlayer = null;
|
|
44
|
+
this.twitchContainer = null;
|
|
45
|
+
this.isPlayerReady = false;
|
|
46
|
+
this.isLive = false;
|
|
47
|
+
|
|
48
|
+
// Get plugin API
|
|
49
|
+
this.api = player.getPluginAPI ? player.getPluginAPI() : {
|
|
50
|
+
player: player,
|
|
51
|
+
video: player.video,
|
|
52
|
+
container: player.container,
|
|
53
|
+
controls: player.controls,
|
|
54
|
+
debug: (msg) => {
|
|
55
|
+
if (this.options.debug) console.log('🎮 Twitch Plugin:', msg);
|
|
56
|
+
},
|
|
57
|
+
triggerEvent: (event, data) => player.triggerEvent(event, data)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Setup plugin
|
|
63
|
+
*/
|
|
64
|
+
setup() {
|
|
65
|
+
this.api.debug('Setup started');
|
|
66
|
+
|
|
67
|
+
// Auto-detect from data attributes
|
|
68
|
+
if (this.options.autoLoadFromData) {
|
|
69
|
+
this.autoDetectSource();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Load Twitch Player SDK
|
|
73
|
+
this.loadTwitchSDK().then(() => {
|
|
74
|
+
if (this.options.channel || this.options.video || this.options.collection) {
|
|
75
|
+
this.createTwitchPlayer();
|
|
76
|
+
}
|
|
77
|
+
}).catch(error => {
|
|
78
|
+
console.error('🎮 Twitch Plugin: Failed to load SDK', error);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Add custom methods to player
|
|
82
|
+
this.addCustomMethods();
|
|
83
|
+
|
|
84
|
+
this.api.debug('Setup completed');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Auto-detect source from data attributes
|
|
89
|
+
*/
|
|
90
|
+
autoDetectSource() {
|
|
91
|
+
// Check data attributes
|
|
92
|
+
const dataChannel = this.api.video.getAttribute('data-twitch-channel');
|
|
93
|
+
const dataVideo = this.api.video.getAttribute('data-twitch-video');
|
|
94
|
+
const dataVideoType = this.api.video.getAttribute('data-video-type');
|
|
95
|
+
|
|
96
|
+
if (dataChannel && dataVideoType === 'twitch') {
|
|
97
|
+
this.options.channel = dataChannel;
|
|
98
|
+
this.api.debug('Channel detected: ' + dataChannel);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (dataVideo && dataVideoType === 'twitch') {
|
|
103
|
+
this.options.video = dataVideo;
|
|
104
|
+
this.api.debug('Video detected: ' + dataVideo);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check video src
|
|
109
|
+
const src = this.api.video.src || this.api.video.currentSrc;
|
|
110
|
+
if (src && this.isTwitchUrl(src)) {
|
|
111
|
+
this.extractFromUrl(src);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Check source elements
|
|
116
|
+
const sources = this.api.video.querySelectorAll('source');
|
|
117
|
+
for (const source of sources) {
|
|
118
|
+
const sourceSrc = source.getAttribute('src');
|
|
119
|
+
const sourceType = source.getAttribute('type');
|
|
120
|
+
|
|
121
|
+
if ((sourceType === 'video/twitch' || this.isTwitchUrl(sourceSrc)) && sourceSrc) {
|
|
122
|
+
this.extractFromUrl(sourceSrc);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if URL is a Twitch URL
|
|
130
|
+
*/
|
|
131
|
+
isTwitchUrl(url) {
|
|
132
|
+
if (!url) return false;
|
|
133
|
+
return /twitch\.tv\/(videos\/|[^/]+\/?$)/.test(url);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Extract channel or video from URL
|
|
138
|
+
*/
|
|
139
|
+
extractFromUrl(url) {
|
|
140
|
+
// Extract video ID: https://www.twitch.tv/videos/123456789
|
|
141
|
+
const videoMatch = url.match(/twitch\.tv\/videos\/(\d+)/);
|
|
142
|
+
if (videoMatch) {
|
|
143
|
+
this.options.video = videoMatch[1];
|
|
144
|
+
this.api.debug('Video ID extracted: ' + this.options.video);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Extract channel: https://www.twitch.tv/channelname
|
|
149
|
+
const channelMatch = url.match(/twitch\.tv\/([^/]+)\/?$/);
|
|
150
|
+
if (channelMatch) {
|
|
151
|
+
this.options.channel = channelMatch[1];
|
|
152
|
+
this.api.debug('Channel extracted: ' + this.options.channel);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Load Twitch Player SDK
|
|
159
|
+
*/
|
|
160
|
+
loadTwitchSDK() {
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
// Check if already loaded
|
|
163
|
+
if (window.Twitch && window.Twitch.Player) {
|
|
164
|
+
this.api.debug('Twitch SDK already loaded');
|
|
165
|
+
resolve();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Inject script
|
|
170
|
+
const script = document.createElement('script');
|
|
171
|
+
script.src = 'https://player.twitch.tv/js/embed/v1.js';
|
|
172
|
+
script.async = true;
|
|
173
|
+
script.onload = () => {
|
|
174
|
+
this.api.debug('Twitch SDK loaded');
|
|
175
|
+
resolve();
|
|
176
|
+
};
|
|
177
|
+
script.onerror = () => reject(new Error('Failed to load Twitch SDK'));
|
|
178
|
+
document.head.appendChild(script);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Create Twitch player
|
|
184
|
+
*/
|
|
185
|
+
createTwitchPlayer() {
|
|
186
|
+
if (!this.options.channel && !this.options.video && !this.options.collection) {
|
|
187
|
+
this.api.debug('No channel, video, or collection specified');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Hide native player
|
|
192
|
+
if (this.options.replaceNativePlayer) {
|
|
193
|
+
this.api.video.style.display = 'none';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Create container
|
|
197
|
+
this.twitchContainer = document.createElement('div');
|
|
198
|
+
this.twitchContainer.id = 'twitch-player-' + Date.now();
|
|
199
|
+
this.twitchContainer.style.cssText = `
|
|
200
|
+
position: absolute;
|
|
201
|
+
top: 0;
|
|
202
|
+
left: 0;
|
|
203
|
+
width: 100%;
|
|
204
|
+
height: 100%;
|
|
205
|
+
z-index: 100;
|
|
206
|
+
`;
|
|
207
|
+
|
|
208
|
+
this.api.container.appendChild(this.twitchContainer);
|
|
209
|
+
|
|
210
|
+
// Configure options
|
|
211
|
+
const playerOptions = {
|
|
212
|
+
width: this.options.width,
|
|
213
|
+
height: this.options.height,
|
|
214
|
+
parent: this.options.parent,
|
|
215
|
+
autoplay: this.options.autoplay,
|
|
216
|
+
muted: this.options.muted,
|
|
217
|
+
allowfullscreen: this.options.allowfullscreen
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Add source
|
|
221
|
+
if (this.options.channel) {
|
|
222
|
+
playerOptions.channel = this.options.channel;
|
|
223
|
+
this.isLive = true;
|
|
224
|
+
} else if (this.options.video) {
|
|
225
|
+
playerOptions.video = this.options.video;
|
|
226
|
+
playerOptions.time = this.options.time;
|
|
227
|
+
this.isLive = false;
|
|
228
|
+
} else if (this.options.collection) {
|
|
229
|
+
playerOptions.collection = this.options.collection;
|
|
230
|
+
this.isLive = false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Create player
|
|
234
|
+
this.twitchPlayer = new Twitch.Player(this.twitchContainer.id, playerOptions);
|
|
235
|
+
|
|
236
|
+
// Setup event listeners
|
|
237
|
+
this.setupEventListeners();
|
|
238
|
+
|
|
239
|
+
this.api.debug('Twitch player created');
|
|
240
|
+
this.api.triggerEvent('twitchplugin:playerready', {
|
|
241
|
+
channel: this.options.channel,
|
|
242
|
+
video: this.options.video,
|
|
243
|
+
isLive: this.isLive
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Setup event listeners
|
|
249
|
+
*/
|
|
250
|
+
setupEventListeners() {
|
|
251
|
+
if (!this.twitchPlayer) return;
|
|
252
|
+
|
|
253
|
+
// Player ready
|
|
254
|
+
this.twitchPlayer.addEventListener(Twitch.Player.READY, () => {
|
|
255
|
+
this.isPlayerReady = true;
|
|
256
|
+
this.api.debug('Player ready');
|
|
257
|
+
this.api.triggerEvent('twitchplugin:ready', {});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Playing
|
|
261
|
+
this.twitchPlayer.addEventListener(Twitch.Player.PLAYING, () => {
|
|
262
|
+
this.api.debug('Playing');
|
|
263
|
+
this.api.triggerEvent('play', {});
|
|
264
|
+
this.api.triggerEvent('playing', {});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Pause
|
|
268
|
+
this.twitchPlayer.addEventListener(Twitch.Player.PAUSE, () => {
|
|
269
|
+
this.api.debug('Paused');
|
|
270
|
+
this.api.triggerEvent('pause', {});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Ended
|
|
274
|
+
this.twitchPlayer.addEventListener(Twitch.Player.ENDED, () => {
|
|
275
|
+
this.api.debug('Ended');
|
|
276
|
+
this.api.triggerEvent('ended', {});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Online (stream went live)
|
|
280
|
+
this.twitchPlayer.addEventListener(Twitch.Player.ONLINE, () => {
|
|
281
|
+
this.api.debug('Stream online');
|
|
282
|
+
this.api.triggerEvent('twitchplugin:online', {});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Offline (stream went offline)
|
|
286
|
+
this.twitchPlayer.addEventListener(Twitch.Player.OFFLINE, () => {
|
|
287
|
+
this.api.debug('Stream offline');
|
|
288
|
+
this.api.triggerEvent('twitchplugin:offline', {});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Playback blocked (autoplay restrictions)
|
|
292
|
+
this.twitchPlayer.addEventListener(Twitch.Player.PLAYBACK_BLOCKED, () => {
|
|
293
|
+
this.api.debug('Playback blocked - user interaction required');
|
|
294
|
+
this.api.triggerEvent('twitchplugin:playbackblocked', {});
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Add custom methods to player instance
|
|
300
|
+
*/
|
|
301
|
+
addCustomMethods() {
|
|
302
|
+
// Load channel
|
|
303
|
+
this.api.player.loadTwitchChannel = (channel) => {
|
|
304
|
+
return this.loadChannel(channel);
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// Load video
|
|
308
|
+
this.api.player.loadTwitchVideo = (videoId, timestamp) => {
|
|
309
|
+
return this.loadVideo(videoId, timestamp);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// Get Twitch player instance
|
|
313
|
+
this.api.player.getTwitchPlayer = () => {
|
|
314
|
+
return this.twitchPlayer;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Check if live
|
|
318
|
+
this.api.player.isTwitchLive = () => {
|
|
319
|
+
return this.isLive;
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Play
|
|
325
|
+
*/
|
|
326
|
+
play() {
|
|
327
|
+
if (!this.twitchPlayer) {
|
|
328
|
+
return Promise.reject('Player not initialized');
|
|
329
|
+
}
|
|
330
|
+
this.twitchPlayer.play();
|
|
331
|
+
return Promise.resolve();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Pause
|
|
336
|
+
*/
|
|
337
|
+
pause() {
|
|
338
|
+
if (!this.twitchPlayer) {
|
|
339
|
+
return Promise.reject('Player not initialized');
|
|
340
|
+
}
|
|
341
|
+
this.twitchPlayer.pause();
|
|
342
|
+
return Promise.resolve();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Seek (VODs only)
|
|
347
|
+
*/
|
|
348
|
+
seek(seconds) {
|
|
349
|
+
if (!this.twitchPlayer) {
|
|
350
|
+
return Promise.reject('Player not initialized');
|
|
351
|
+
}
|
|
352
|
+
if (this.isLive) {
|
|
353
|
+
console.warn('🎮 Twitch Plugin: Cannot seek in live streams');
|
|
354
|
+
return Promise.reject('Cannot seek in live streams');
|
|
355
|
+
}
|
|
356
|
+
this.twitchPlayer.seek(seconds);
|
|
357
|
+
return Promise.resolve(seconds);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Get current time
|
|
362
|
+
*/
|
|
363
|
+
getCurrentTime() {
|
|
364
|
+
if (!this.twitchPlayer) {
|
|
365
|
+
return Promise.reject('Player not initialized');
|
|
366
|
+
}
|
|
367
|
+
return Promise.resolve(this.twitchPlayer.getCurrentTime());
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get duration (VODs only)
|
|
372
|
+
*/
|
|
373
|
+
getDuration() {
|
|
374
|
+
if (!this.twitchPlayer) {
|
|
375
|
+
return Promise.reject('Player not initialized');
|
|
376
|
+
}
|
|
377
|
+
if (this.isLive) {
|
|
378
|
+
return Promise.resolve(0);
|
|
379
|
+
}
|
|
380
|
+
return Promise.resolve(this.twitchPlayer.getDuration());
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Set volume
|
|
385
|
+
*/
|
|
386
|
+
setVolume(volume) {
|
|
387
|
+
if (!this.twitchPlayer) {
|
|
388
|
+
return Promise.reject('Player not initialized');
|
|
389
|
+
}
|
|
390
|
+
this.twitchPlayer.setVolume(volume);
|
|
391
|
+
return Promise.resolve(volume);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Get volume
|
|
396
|
+
*/
|
|
397
|
+
getVolume() {
|
|
398
|
+
if (!this.twitchPlayer) {
|
|
399
|
+
return Promise.reject('Player not initialized');
|
|
400
|
+
}
|
|
401
|
+
return Promise.resolve(this.twitchPlayer.getVolume());
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Mute
|
|
406
|
+
*/
|
|
407
|
+
setMuted(muted) {
|
|
408
|
+
if (!this.twitchPlayer) {
|
|
409
|
+
return Promise.reject('Player not initialized');
|
|
410
|
+
}
|
|
411
|
+
this.twitchPlayer.setMuted(muted);
|
|
412
|
+
return Promise.resolve(muted);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Get muted state
|
|
417
|
+
*/
|
|
418
|
+
getMuted() {
|
|
419
|
+
if (!this.twitchPlayer) {
|
|
420
|
+
return Promise.reject('Player not initialized');
|
|
421
|
+
}
|
|
422
|
+
return Promise.resolve(this.twitchPlayer.getMuted());
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Get quality
|
|
427
|
+
*/
|
|
428
|
+
getQuality() {
|
|
429
|
+
if (!this.twitchPlayer) {
|
|
430
|
+
return Promise.reject('Player not initialized');
|
|
431
|
+
}
|
|
432
|
+
return Promise.resolve(this.twitchPlayer.getQuality());
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Set quality
|
|
437
|
+
*/
|
|
438
|
+
setQuality(quality) {
|
|
439
|
+
if (!this.twitchPlayer) {
|
|
440
|
+
return Promise.reject('Player not initialized');
|
|
441
|
+
}
|
|
442
|
+
this.twitchPlayer.setQuality(quality);
|
|
443
|
+
return Promise.resolve(quality);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Get available qualities
|
|
448
|
+
*/
|
|
449
|
+
getQualities() {
|
|
450
|
+
if (!this.twitchPlayer) {
|
|
451
|
+
return Promise.reject('Player not initialized');
|
|
452
|
+
}
|
|
453
|
+
return Promise.resolve(this.twitchPlayer.getPlaybackStats().qualitiesAvailable);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Get channel name
|
|
458
|
+
*/
|
|
459
|
+
getChannel() {
|
|
460
|
+
if (!this.twitchPlayer) {
|
|
461
|
+
return Promise.reject('Player not initialized');
|
|
462
|
+
}
|
|
463
|
+
return Promise.resolve(this.twitchPlayer.getChannel());
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Get video ID
|
|
468
|
+
*/
|
|
469
|
+
getVideo() {
|
|
470
|
+
if (!this.twitchPlayer) {
|
|
471
|
+
return Promise.reject('Player not initialized');
|
|
472
|
+
}
|
|
473
|
+
return Promise.resolve(this.twitchPlayer.getVideo());
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Get playback stats
|
|
478
|
+
*/
|
|
479
|
+
getPlaybackStats() {
|
|
480
|
+
if (!this.twitchPlayer) {
|
|
481
|
+
return Promise.reject('Player not initialized');
|
|
482
|
+
}
|
|
483
|
+
return Promise.resolve(this.twitchPlayer.getPlaybackStats());
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Check if paused
|
|
488
|
+
*/
|
|
489
|
+
isPaused() {
|
|
490
|
+
if (!this.twitchPlayer) {
|
|
491
|
+
return Promise.reject('Player not initialized');
|
|
492
|
+
}
|
|
493
|
+
return Promise.resolve(this.twitchPlayer.isPaused());
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Load channel (live stream)
|
|
498
|
+
*/
|
|
499
|
+
loadChannel(channel) {
|
|
500
|
+
if (!this.twitchPlayer) {
|
|
501
|
+
return Promise.reject('Player not initialized');
|
|
502
|
+
}
|
|
503
|
+
this.options.channel = channel;
|
|
504
|
+
this.options.video = null;
|
|
505
|
+
this.isLive = true;
|
|
506
|
+
this.twitchPlayer.setChannel(channel);
|
|
507
|
+
this.api.triggerEvent('twitchplugin:channelloaded', { channel });
|
|
508
|
+
return Promise.resolve(channel);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Load video (VOD)
|
|
513
|
+
*/
|
|
514
|
+
loadVideo(videoId, timestamp = '0h0m0s') {
|
|
515
|
+
if (!this.twitchPlayer) {
|
|
516
|
+
return Promise.reject('Player not initialized');
|
|
517
|
+
}
|
|
518
|
+
this.options.video = videoId;
|
|
519
|
+
this.options.channel = null;
|
|
520
|
+
this.isLive = false;
|
|
521
|
+
this.twitchPlayer.setVideo(videoId, timestamp);
|
|
522
|
+
this.api.triggerEvent('twitchplugin:videoloaded', { video: videoId, timestamp });
|
|
523
|
+
return Promise.resolve(videoId);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Load collection
|
|
528
|
+
*/
|
|
529
|
+
loadCollection(collectionId, videoId) {
|
|
530
|
+
if (!this.twitchPlayer) {
|
|
531
|
+
return Promise.reject('Player not initialized');
|
|
532
|
+
}
|
|
533
|
+
this.options.collection = collectionId;
|
|
534
|
+
this.isLive = false;
|
|
535
|
+
this.twitchPlayer.setCollection(collectionId, videoId);
|
|
536
|
+
this.api.triggerEvent('twitchplugin:collectionloaded', { collection: collectionId, video: videoId });
|
|
537
|
+
return Promise.resolve(collectionId);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Dispose plugin
|
|
542
|
+
*/
|
|
543
|
+
dispose() {
|
|
544
|
+
this.api.debug('Disposing plugin');
|
|
545
|
+
|
|
546
|
+
if (this.twitchContainer) {
|
|
547
|
+
this.twitchContainer.remove();
|
|
548
|
+
this.twitchContainer = null;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
this.twitchPlayer = null;
|
|
552
|
+
|
|
553
|
+
// Restore native player
|
|
554
|
+
if (this.api.video && this.options.replaceNativePlayer) {
|
|
555
|
+
this.api.video.style.display = '';
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
this.api.debug('Plugin disposed');
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Register plugin globally
|
|
563
|
+
if (typeof window.registerMYETVPlugin === 'function') {
|
|
564
|
+
window.registerMYETVPlugin('twitch', TwitchPlugin);
|
|
565
|
+
} else {
|
|
566
|
+
console.error('🎮 MYETV Player plugin system not found');
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|