myetv-player 1.0.8 → 1.1.0
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 +76 -2
- package/css/myetv-player.css +321 -208
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +219 -37
- package/dist/myetv-player.min.js +204 -26
- package/package.json +3 -1
- package/plugins/cloudflare/README.md +26 -4
- package/plugins/cloudflare/myetv-player-cloudflare-stream-plugin.js +1273 -217
- package/plugins/facebook/myetv-player-facebook-plugin.js +1340 -164
- package/plugins/twitch/myetv-player-twitch-plugin.js +428 -167
- package/plugins/vimeo/README.md +1 -1
- package/plugins/vimeo/myetv-player-vimeo.js +560 -247
- package/plugins/youtube/README.md +18 -7
- package/plugins/youtube/myetv-player-youtube-plugin.js +1485 -190
- package/scss/_base.scss +0 -15
- package/scss/_controls.scss +182 -2
- package/scss/_menus.scss +51 -0
- package/scss/_responsive.scss +187 -321
- package/scss/_title-overlay.scss +27 -0
- package/scss/_video.scss +0 -75
- package/scss/_watermark.scss +120 -0
- package/scss/myetv-player.scss +7 -7
- package/src/controls.js +72 -21
- package/src/core.js +43 -5
- package/src/events.js +33 -5
- package/src/utils.js +20 -6
- package/src/watermark.js +51 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
7
|
|
|
8
8
|
(function () {
|
|
9
9
|
'use strict';
|
|
@@ -12,40 +12,31 @@
|
|
|
12
12
|
constructor(player, options = {}) {
|
|
13
13
|
this.player = player;
|
|
14
14
|
this.options = {
|
|
15
|
-
// Video source (channel for live, video for VOD, collection for collections)
|
|
16
15
|
channel: options.channel || null,
|
|
17
16
|
video: options.video || null,
|
|
18
17
|
collection: options.collection || null,
|
|
19
|
-
|
|
20
|
-
// Parent domains (REQUIRED for embedding)
|
|
21
18
|
parent: options.parent || [window.location.hostname],
|
|
22
|
-
|
|
23
|
-
// Player dimensions
|
|
24
19
|
width: options.width || '100%',
|
|
25
20
|
height: options.height || '100%',
|
|
26
|
-
|
|
27
|
-
// Playback options
|
|
28
21
|
autoplay: options.autoplay !== false,
|
|
29
22
|
muted: options.muted || false,
|
|
30
|
-
time: options.time || '0h0m0s',
|
|
31
|
-
|
|
32
|
-
// UI options
|
|
23
|
+
time: options.time || '0h0m0s',
|
|
33
24
|
allowfullscreen: options.allowfullscreen !== false,
|
|
34
|
-
|
|
35
|
-
// Plugin options
|
|
36
25
|
debug: options.debug || false,
|
|
37
26
|
replaceNativePlayer: options.replaceNativePlayer !== false,
|
|
38
27
|
autoLoadFromData: options.autoLoadFromData !== false,
|
|
39
|
-
|
|
40
28
|
...options
|
|
41
29
|
};
|
|
42
30
|
|
|
43
31
|
this.twitchPlayer = null;
|
|
44
32
|
this.twitchContainer = null;
|
|
33
|
+
this.twitchIframe = null;
|
|
45
34
|
this.isPlayerReady = false;
|
|
46
35
|
this.isLive = false;
|
|
36
|
+
this.isPausedState = true; // Track state manually
|
|
37
|
+
this.extractedBrandLogo = null; // Store reference to extracted brand logo
|
|
38
|
+
this.brandLogoHideTimer = null; // Timer for auto-hide
|
|
47
39
|
|
|
48
|
-
// Get plugin API
|
|
49
40
|
this.api = player.getPluginAPI ? player.getPluginAPI() : {
|
|
50
41
|
player: player,
|
|
51
42
|
video: player.video,
|
|
@@ -58,18 +49,12 @@
|
|
|
58
49
|
};
|
|
59
50
|
}
|
|
60
51
|
|
|
61
|
-
/**
|
|
62
|
-
* Setup plugin
|
|
63
|
-
*/
|
|
64
52
|
setup() {
|
|
65
|
-
this.api.debug('Setup started');
|
|
66
|
-
|
|
67
|
-
// Auto-detect from data attributes
|
|
53
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Setup started');
|
|
68
54
|
if (this.options.autoLoadFromData) {
|
|
69
55
|
this.autoDetectSource();
|
|
70
56
|
}
|
|
71
57
|
|
|
72
|
-
// Load Twitch Player SDK
|
|
73
58
|
this.loadTwitchSDK().then(() => {
|
|
74
59
|
if (this.options.channel || this.options.video || this.options.collection) {
|
|
75
60
|
this.createTwitchPlayer();
|
|
@@ -78,46 +63,81 @@
|
|
|
78
63
|
console.error('🎮 Twitch Plugin: Failed to load SDK', error);
|
|
79
64
|
});
|
|
80
65
|
|
|
81
|
-
// Add custom methods to player
|
|
82
66
|
this.addCustomMethods();
|
|
67
|
+
this.syncControls();
|
|
68
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Setup completed');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
syncControls() {
|
|
72
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Syncing controls');
|
|
73
|
+
|
|
74
|
+
// Override play method
|
|
75
|
+
const originalPlay = this.player.play;
|
|
76
|
+
this.player.play = () => {
|
|
77
|
+
if (this.twitchPlayer && this.isPlayerReady) {
|
|
78
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Play called');
|
|
79
|
+
try {
|
|
80
|
+
this.twitchPlayer.play();
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (this.api.player.options.debug) console.error('🎮 Twitch Plugin: Play error:', error);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
if (originalPlay) originalPlay.call(this.player);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Override pause method
|
|
90
|
+
const originalPause = this.player.pause;
|
|
91
|
+
this.player.pause = () => {
|
|
92
|
+
if (this.twitchPlayer && this.isPlayerReady) {
|
|
93
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Pause called');
|
|
94
|
+
try {
|
|
95
|
+
this.twitchPlayer.pause();
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (this.api.player.options.debug) console.error('🎮 Twitch Plugin: Pause error:', error);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
if (originalPause) originalPause.call(this.player);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
83
103
|
|
|
84
|
-
|
|
104
|
+
// DON'T override paused getter - let the UI handle it via icon changes
|
|
105
|
+
|
|
106
|
+
// Hide PiP and Speed buttons
|
|
107
|
+
const pipButton = this.api.container.querySelector('.pip-btn');
|
|
108
|
+
if (pipButton) pipButton.style.display = 'none';
|
|
109
|
+
|
|
110
|
+
const speedButton = this.api.container.querySelector('.speed-btn');
|
|
111
|
+
if (speedButton) speedButton.style.display = 'none';
|
|
85
112
|
}
|
|
86
113
|
|
|
87
|
-
/**
|
|
88
|
-
* Auto-detect source from data attributes
|
|
89
|
-
*/
|
|
90
114
|
autoDetectSource() {
|
|
91
|
-
// Check data attributes
|
|
92
115
|
const dataChannel = this.api.video.getAttribute('data-twitch-channel');
|
|
93
116
|
const dataVideo = this.api.video.getAttribute('data-twitch-video');
|
|
94
117
|
const dataVideoType = this.api.video.getAttribute('data-video-type');
|
|
95
118
|
|
|
96
119
|
if (dataChannel && dataVideoType === 'twitch') {
|
|
97
120
|
this.options.channel = dataChannel;
|
|
98
|
-
this.api.debug('Channel detected:
|
|
121
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Channel detected:', dataChannel);
|
|
99
122
|
return;
|
|
100
123
|
}
|
|
101
124
|
|
|
102
125
|
if (dataVideo && dataVideoType === 'twitch') {
|
|
103
126
|
this.options.video = dataVideo;
|
|
104
|
-
this.api.debug('Video detected:
|
|
127
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Video detected:', dataVideo);
|
|
105
128
|
return;
|
|
106
129
|
}
|
|
107
130
|
|
|
108
|
-
// Check video src
|
|
109
131
|
const src = this.api.video.src || this.api.video.currentSrc;
|
|
110
132
|
if (src && this.isTwitchUrl(src)) {
|
|
111
133
|
this.extractFromUrl(src);
|
|
112
134
|
return;
|
|
113
135
|
}
|
|
114
136
|
|
|
115
|
-
// Check source elements
|
|
116
137
|
const sources = this.api.video.querySelectorAll('source');
|
|
117
138
|
for (const source of sources) {
|
|
118
139
|
const sourceSrc = source.getAttribute('src');
|
|
119
140
|
const sourceType = source.getAttribute('type');
|
|
120
|
-
|
|
121
141
|
if ((sourceType === 'video/twitch' || this.isTwitchUrl(sourceSrc)) && sourceSrc) {
|
|
122
142
|
this.extractFromUrl(sourceSrc);
|
|
123
143
|
return;
|
|
@@ -125,53 +145,40 @@
|
|
|
125
145
|
}
|
|
126
146
|
}
|
|
127
147
|
|
|
128
|
-
/**
|
|
129
|
-
* Check if URL is a Twitch URL
|
|
130
|
-
*/
|
|
131
148
|
isTwitchUrl(url) {
|
|
132
149
|
if (!url) return false;
|
|
133
150
|
return /twitch\.tv\/(videos\/|[^/]+\/?$)/.test(url);
|
|
134
151
|
}
|
|
135
152
|
|
|
136
|
-
/**
|
|
137
|
-
* Extract channel or video from URL
|
|
138
|
-
*/
|
|
139
153
|
extractFromUrl(url) {
|
|
140
|
-
// Extract video ID: https://www.twitch.tv/videos/123456789
|
|
141
154
|
const videoMatch = url.match(/twitch\.tv\/videos\/(\d+)/);
|
|
142
155
|
if (videoMatch) {
|
|
143
156
|
this.options.video = videoMatch[1];
|
|
144
|
-
this.api.debug('Video ID extracted:
|
|
157
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Video ID extracted:', this.options.video);
|
|
145
158
|
return;
|
|
146
159
|
}
|
|
147
160
|
|
|
148
|
-
// Extract channel: https://www.twitch.tv/channelname
|
|
149
161
|
const channelMatch = url.match(/twitch\.tv\/([^/]+)\/?$/);
|
|
150
162
|
if (channelMatch) {
|
|
151
163
|
this.options.channel = channelMatch[1];
|
|
152
|
-
this.api.debug('Channel extracted:
|
|
164
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Channel extracted:', this.options.channel);
|
|
153
165
|
return;
|
|
154
166
|
}
|
|
155
167
|
}
|
|
156
168
|
|
|
157
|
-
/**
|
|
158
|
-
* Load Twitch Player SDK
|
|
159
|
-
*/
|
|
160
169
|
loadTwitchSDK() {
|
|
161
170
|
return new Promise((resolve, reject) => {
|
|
162
|
-
// Check if already loaded
|
|
163
171
|
if (window.Twitch && window.Twitch.Player) {
|
|
164
|
-
this.api.debug('Twitch SDK already loaded');
|
|
172
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: SDK already loaded');
|
|
165
173
|
resolve();
|
|
166
174
|
return;
|
|
167
175
|
}
|
|
168
176
|
|
|
169
|
-
// Inject script
|
|
170
177
|
const script = document.createElement('script');
|
|
171
178
|
script.src = 'https://player.twitch.tv/js/embed/v1.js';
|
|
172
179
|
script.async = true;
|
|
173
180
|
script.onload = () => {
|
|
174
|
-
this.api.debug('Twitch SDK loaded');
|
|
181
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: SDK loaded');
|
|
175
182
|
resolve();
|
|
176
183
|
};
|
|
177
184
|
script.onerror = () => reject(new Error('Failed to load Twitch SDK'));
|
|
@@ -179,45 +186,38 @@
|
|
|
179
186
|
});
|
|
180
187
|
}
|
|
181
188
|
|
|
182
|
-
/**
|
|
183
|
-
* Create Twitch player
|
|
184
|
-
*/
|
|
185
189
|
createTwitchPlayer() {
|
|
186
190
|
if (!this.options.channel && !this.options.video && !this.options.collection) {
|
|
187
|
-
this.api.debug('No channel, video, or collection specified');
|
|
191
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: No channel, video, or collection specified');
|
|
188
192
|
return;
|
|
189
193
|
}
|
|
190
194
|
|
|
191
|
-
// Hide native player
|
|
192
195
|
if (this.options.replaceNativePlayer) {
|
|
193
196
|
this.api.video.style.display = 'none';
|
|
194
197
|
}
|
|
195
198
|
|
|
196
|
-
// Create container
|
|
197
199
|
this.twitchContainer = document.createElement('div');
|
|
198
200
|
this.twitchContainer.id = 'twitch-player-' + Date.now();
|
|
199
201
|
this.twitchContainer.style.cssText = `
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
202
|
+
position: fixed;
|
|
203
|
+
top: 0;
|
|
204
|
+
left: 0;
|
|
205
|
+
width: 100vw;
|
|
206
|
+
height: 100vh;
|
|
207
|
+
z-index: 1;
|
|
208
|
+
`;
|
|
207
209
|
|
|
208
210
|
this.api.container.appendChild(this.twitchContainer);
|
|
209
211
|
|
|
210
|
-
// Configure options
|
|
211
212
|
const playerOptions = {
|
|
212
|
-
width:
|
|
213
|
-
height:
|
|
213
|
+
width: '100%',
|
|
214
|
+
height: '100%',
|
|
214
215
|
parent: this.options.parent,
|
|
215
|
-
autoplay:
|
|
216
|
+
autoplay: false,
|
|
216
217
|
muted: this.options.muted,
|
|
217
218
|
allowfullscreen: this.options.allowfullscreen
|
|
218
219
|
};
|
|
219
220
|
|
|
220
|
-
// Add source
|
|
221
221
|
if (this.options.channel) {
|
|
222
222
|
playerOptions.channel = this.options.channel;
|
|
223
223
|
this.isLive = true;
|
|
@@ -230,13 +230,11 @@
|
|
|
230
230
|
this.isLive = false;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
// Create player
|
|
234
233
|
this.twitchPlayer = new Twitch.Player(this.twitchContainer.id, playerOptions);
|
|
235
|
-
|
|
236
|
-
// Setup event listeners
|
|
237
234
|
this.setupEventListeners();
|
|
238
235
|
|
|
239
|
-
this.api.debug('Twitch
|
|
236
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Player created');
|
|
237
|
+
|
|
240
238
|
this.api.triggerEvent('twitchplugin:playerready', {
|
|
241
239
|
channel: this.options.channel,
|
|
242
240
|
video: this.options.video,
|
|
@@ -244,322 +242,585 @@
|
|
|
244
242
|
});
|
|
245
243
|
}
|
|
246
244
|
|
|
247
|
-
/**
|
|
248
|
-
* Setup event listeners
|
|
249
|
-
*/
|
|
250
245
|
setupEventListeners() {
|
|
251
246
|
if (!this.twitchPlayer) return;
|
|
252
247
|
|
|
253
|
-
// Player ready
|
|
254
248
|
this.twitchPlayer.addEventListener(Twitch.Player.READY, () => {
|
|
255
249
|
this.isPlayerReady = true;
|
|
256
|
-
this.
|
|
250
|
+
this.isPausedState = true; // Start paused
|
|
251
|
+
|
|
252
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Player ready - paused state:', this.isPausedState);
|
|
253
|
+
|
|
254
|
+
this.twitchIframe = this.twitchContainer.querySelector('iframe');
|
|
255
|
+
if (this.twitchIframe) {
|
|
256
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Iframe ready');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const loadingOverlay = this.api.container.querySelector('.loading-overlay');
|
|
260
|
+
if (loadingOverlay) loadingOverlay.style.display = 'none';
|
|
261
|
+
|
|
262
|
+
const posterOverlay = this.api.container.querySelector('.video-poster-overlay');
|
|
263
|
+
if (posterOverlay) posterOverlay.style.display = 'none';
|
|
264
|
+
|
|
265
|
+
if (this.api.video) {
|
|
266
|
+
this.api.video.style.display = 'none';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
this.api.container.style.height = '100%';
|
|
270
|
+
|
|
271
|
+
// **CRITICAL: Extract brand logo BEFORE hiding controlbar**
|
|
272
|
+
this.extractAndRepositionBrandLogo();
|
|
273
|
+
|
|
274
|
+
// **Hide controlbar completely AFTER extracting brand logo**
|
|
275
|
+
if (this.api.controls) {
|
|
276
|
+
this.api.controls.style.display = 'none';
|
|
277
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Controlbar hidden');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// **Hide title overlay (Twitch already has it)**
|
|
281
|
+
const titleOverlay = this.api.container.querySelector('.title-overlay');
|
|
282
|
+
if (titleOverlay) {
|
|
283
|
+
titleOverlay.style.display = 'none';
|
|
284
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Title overlay hidden');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this.setupResizeAndFullscreenListeners();
|
|
257
288
|
this.api.triggerEvent('twitchplugin:ready', {});
|
|
258
289
|
});
|
|
259
290
|
|
|
260
|
-
// Playing
|
|
261
291
|
this.twitchPlayer.addEventListener(Twitch.Player.PLAYING, () => {
|
|
262
|
-
this.api.debug
|
|
263
|
-
|
|
264
|
-
|
|
292
|
+
if (this.api.player.options.debug) {
|
|
293
|
+
console.log('🟢 Twitch Plugin: PLAYING event');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
this.isPausedState = false;
|
|
297
|
+
|
|
298
|
+
// Update UI icons
|
|
299
|
+
const loadingOverlay = this.api.container.querySelector('.loading-overlay');
|
|
300
|
+
if (loadingOverlay) loadingOverlay.style.display = 'none';
|
|
301
|
+
|
|
302
|
+
const playIcon = this.api.container.querySelector('.play-icon');
|
|
303
|
+
const pauseIcon = this.api.container.querySelector('.pause-icon');
|
|
304
|
+
if (playIcon && pauseIcon) {
|
|
305
|
+
playIcon.classList.add('hidden');
|
|
306
|
+
pauseIcon.classList.remove('hidden');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Dispatch native video events
|
|
310
|
+
const playEvent = new Event('play');
|
|
311
|
+
const playingEvent = new Event('playing');
|
|
312
|
+
this.api.video.dispatchEvent(playEvent);
|
|
313
|
+
this.api.video.dispatchEvent(playingEvent);
|
|
314
|
+
|
|
315
|
+
// **Manually trigger the 'played' callbacks**
|
|
316
|
+
const targetPlayer = this.api.player || this.player;
|
|
317
|
+
if (targetPlayer.eventCallbacks && targetPlayer.eventCallbacks['played']) {
|
|
318
|
+
const eventData = {
|
|
319
|
+
type: 'played',
|
|
320
|
+
timestamp: Date.now(),
|
|
321
|
+
player: targetPlayer,
|
|
322
|
+
currentTime: this.getCurrentTime(),
|
|
323
|
+
duration: this.getDuration()
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
targetPlayer.eventCallbacks['played'].forEach((callback, index) => {
|
|
327
|
+
try {
|
|
328
|
+
callback(eventData);
|
|
329
|
+
if (this.api.player.options.debug) console.log(`✅ Played callback #${index} executed`);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.error(`❌ Error in played callback #${index}:`, error);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
265
335
|
});
|
|
266
336
|
|
|
267
|
-
// Pause
|
|
268
337
|
this.twitchPlayer.addEventListener(Twitch.Player.PAUSE, () => {
|
|
269
|
-
this.api.debug
|
|
270
|
-
|
|
338
|
+
if (this.api.player.options.debug) {
|
|
339
|
+
console.log('🔴 Twitch Plugin: PAUSE event');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
this.isPausedState = true;
|
|
343
|
+
|
|
344
|
+
const playIcon = this.api.container.querySelector('.play-icon');
|
|
345
|
+
const pauseIcon = this.api.container.querySelector('.pause-icon');
|
|
346
|
+
if (playIcon && pauseIcon) {
|
|
347
|
+
playIcon.classList.remove('hidden');
|
|
348
|
+
pauseIcon.classList.add('hidden');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Dispatch native pause event
|
|
352
|
+
const pauseEvent = new Event('pause');
|
|
353
|
+
this.api.video.dispatchEvent(pauseEvent);
|
|
354
|
+
|
|
355
|
+
// **Manually trigger the 'paused' callbacks**
|
|
356
|
+
const targetPlayer = this.api.player || this.player;
|
|
357
|
+
if (targetPlayer.eventCallbacks && targetPlayer.eventCallbacks['paused']) {
|
|
358
|
+
const eventData = {
|
|
359
|
+
type: 'paused',
|
|
360
|
+
timestamp: Date.now(),
|
|
361
|
+
player: targetPlayer,
|
|
362
|
+
currentTime: this.getCurrentTime(),
|
|
363
|
+
duration: this.getDuration()
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
targetPlayer.eventCallbacks['paused'].forEach((callback, index) => {
|
|
367
|
+
try {
|
|
368
|
+
callback(eventData);
|
|
369
|
+
if (this.api.player.options.debug) console.log(`✅ Paused callback #${index} executed`);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error(`❌ Error in paused callback #${index}:`, error);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
271
375
|
});
|
|
272
376
|
|
|
273
|
-
// Ended
|
|
274
377
|
this.twitchPlayer.addEventListener(Twitch.Player.ENDED, () => {
|
|
275
|
-
this.api.debug('Ended');
|
|
378
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Ended');
|
|
379
|
+
this.isPausedState = true;
|
|
380
|
+
|
|
381
|
+
const playIcon = this.api.container.querySelector('.play-icon');
|
|
382
|
+
const pauseIcon = this.api.container.querySelector('.pause-icon');
|
|
383
|
+
if (playIcon && pauseIcon) {
|
|
384
|
+
playIcon.classList.remove('hidden');
|
|
385
|
+
pauseIcon.classList.add('hidden');
|
|
386
|
+
}
|
|
387
|
+
|
|
276
388
|
this.api.triggerEvent('ended', {});
|
|
277
389
|
});
|
|
278
390
|
|
|
279
|
-
// Online (stream went live)
|
|
280
391
|
this.twitchPlayer.addEventListener(Twitch.Player.ONLINE, () => {
|
|
281
|
-
this.api.debug('Stream online');
|
|
392
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Stream online');
|
|
282
393
|
this.api.triggerEvent('twitchplugin:online', {});
|
|
283
394
|
});
|
|
284
395
|
|
|
285
|
-
// Offline (stream went offline)
|
|
286
396
|
this.twitchPlayer.addEventListener(Twitch.Player.OFFLINE, () => {
|
|
287
|
-
this.api.debug('Stream offline');
|
|
397
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Stream offline');
|
|
398
|
+
this.isPausedState = true;
|
|
288
399
|
this.api.triggerEvent('twitchplugin:offline', {});
|
|
289
400
|
});
|
|
290
401
|
|
|
291
|
-
// Playback blocked (autoplay restrictions)
|
|
292
402
|
this.twitchPlayer.addEventListener(Twitch.Player.PLAYBACK_BLOCKED, () => {
|
|
293
|
-
this.api.debug('
|
|
403
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Playback blocked');
|
|
404
|
+
this.isPausedState = true;
|
|
294
405
|
this.api.triggerEvent('twitchplugin:playbackblocked', {});
|
|
406
|
+
console.warn('🎮 Twitch: Click on the video to start playback');
|
|
295
407
|
});
|
|
296
408
|
}
|
|
297
409
|
|
|
298
410
|
/**
|
|
299
|
-
|
|
300
|
-
|
|
411
|
+
* Extract brandlogo from controlbar and reposition it in BOTTOM-RIGHT corner
|
|
412
|
+
* With auto-hide behavior like the controlbar
|
|
413
|
+
* MUST be called BEFORE hiding the controlbar
|
|
414
|
+
*/
|
|
415
|
+
extractAndRepositionBrandLogo() {
|
|
416
|
+
// Find brandlogo inside controlbar (correct selector: .brand-logo with hyphen)
|
|
417
|
+
const brandLogo = this.api.container.querySelector('.brand-logo');
|
|
418
|
+
|
|
419
|
+
if (brandLogo) {
|
|
420
|
+
// Store reference
|
|
421
|
+
this.extractedBrandLogo = brandLogo;
|
|
422
|
+
|
|
423
|
+
// Store original parent for restoration
|
|
424
|
+
if (!brandLogo._originalParent) {
|
|
425
|
+
brandLogo._originalParent = brandLogo.parentElement;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Extract from controlbar and append directly to container
|
|
429
|
+
// This way it won't be hidden when controlbar is hidden
|
|
430
|
+
this.api.container.appendChild(brandLogo);
|
|
431
|
+
|
|
432
|
+
// Apply new positioning styles - BOTTOM-RIGHT corner, closer to Twitch controls
|
|
433
|
+
brandLogo.style.cssText = `
|
|
434
|
+
position: absolute !important;
|
|
435
|
+
bottom: 35px !important;
|
|
436
|
+
right: 20px !important;
|
|
437
|
+
z-index: 2000 !important;
|
|
438
|
+
pointer-events: auto !important;
|
|
439
|
+
display: block !important;
|
|
440
|
+
opacity: 0 !important;
|
|
441
|
+
transition: opacity 0.3s ease !important;
|
|
442
|
+
`;
|
|
443
|
+
|
|
444
|
+
// Setup auto-hide behavior (like controlbar)
|
|
445
|
+
this.setupBrandLogoAutoHide(brandLogo);
|
|
446
|
+
|
|
447
|
+
if (this.api.player.options.debug) {
|
|
448
|
+
console.log('🎮 Twitch Plugin: Brand logo extracted with auto-hide behavior');
|
|
449
|
+
}
|
|
450
|
+
} else {
|
|
451
|
+
if (this.api.player.options.debug) {
|
|
452
|
+
console.log('🎮 Twitch Plugin: No brand logo found (.brand-logo)');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Setup auto-hide behavior for brand logo (shows on mousemove, hides after 3 seconds)
|
|
459
|
+
*/
|
|
460
|
+
setupBrandLogoAutoHide(brandLogo) {
|
|
461
|
+
// Show logo initially for 3 seconds, then hide
|
|
462
|
+
this.showBrandLogo();
|
|
463
|
+
this.scheduleBrandLogoHide();
|
|
464
|
+
|
|
465
|
+
// Mouse move handler - show logo when mouse moves
|
|
466
|
+
const mouseMoveHandler = () => {
|
|
467
|
+
this.showBrandLogo();
|
|
468
|
+
this.scheduleBrandLogoHide();
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// Add mouse move listener to container
|
|
472
|
+
this.api.container.addEventListener('mousemove', mouseMoveHandler);
|
|
473
|
+
this.api.container.addEventListener('mouseenter', mouseMoveHandler);
|
|
474
|
+
|
|
475
|
+
// Store handler for cleanup
|
|
476
|
+
if (!this.api.container._brandLogoMouseHandler) {
|
|
477
|
+
this.api.container._brandLogoMouseHandler = mouseMoveHandler;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Mouse leave handler - hide immediately when mouse leaves
|
|
481
|
+
const mouseLeaveHandler = () => {
|
|
482
|
+
this.hideBrandLogo();
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
this.api.container.addEventListener('mouseleave', mouseLeaveHandler);
|
|
486
|
+
|
|
487
|
+
// Store handler for cleanup
|
|
488
|
+
if (!this.api.container._brandLogoLeaveHandler) {
|
|
489
|
+
this.api.container._brandLogoLeaveHandler = mouseLeaveHandler;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Show brand logo (fade in)
|
|
495
|
+
*/
|
|
496
|
+
showBrandLogo() {
|
|
497
|
+
if (this.extractedBrandLogo) {
|
|
498
|
+
this.extractedBrandLogo.style.opacity = '0.8';
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Hide brand logo (fade out)
|
|
504
|
+
*/
|
|
505
|
+
hideBrandLogo() {
|
|
506
|
+
if (this.extractedBrandLogo) {
|
|
507
|
+
this.extractedBrandLogo.style.opacity = '0';
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Schedule brand logo to hide after 3 seconds of inactivity
|
|
513
|
+
*/
|
|
514
|
+
scheduleBrandLogoHide() {
|
|
515
|
+
// Clear existing timer
|
|
516
|
+
if (this.brandLogoHideTimer) {
|
|
517
|
+
clearTimeout(this.brandLogoHideTimer);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Set new timer to hide after 3 seconds
|
|
521
|
+
this.brandLogoHideTimer = setTimeout(() => {
|
|
522
|
+
this.hideBrandLogo();
|
|
523
|
+
}, 3000);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
setupResizeAndFullscreenListeners() {
|
|
527
|
+
this.resizeHandler = () => {
|
|
528
|
+
this.api.container.style.height = '100%';
|
|
529
|
+
// Logo is already positioned with fixed coordinates, no need to reposition
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
window.addEventListener('resize', this.resizeHandler);
|
|
533
|
+
|
|
534
|
+
this.fullscreenChangeHandler = () => {
|
|
535
|
+
setTimeout(() => {
|
|
536
|
+
this.api.container.style.height = '100%';
|
|
537
|
+
}, 100);
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
document.addEventListener('fullscreenchange', this.fullscreenChangeHandler);
|
|
541
|
+
document.addEventListener('webkitfullscreenchange', this.fullscreenChangeHandler);
|
|
542
|
+
document.addEventListener('mozfullscreenchange', this.fullscreenChangeHandler);
|
|
543
|
+
document.addEventListener('MSFullscreenChange', this.fullscreenChangeHandler);
|
|
544
|
+
}
|
|
545
|
+
|
|
301
546
|
addCustomMethods() {
|
|
302
|
-
// Load channel
|
|
303
547
|
this.api.player.loadTwitchChannel = (channel) => {
|
|
304
548
|
return this.loadChannel(channel);
|
|
305
549
|
};
|
|
306
550
|
|
|
307
|
-
// Load video
|
|
308
551
|
this.api.player.loadTwitchVideo = (videoId, timestamp) => {
|
|
309
552
|
return this.loadVideo(videoId, timestamp);
|
|
310
553
|
};
|
|
311
554
|
|
|
312
|
-
// Get Twitch player instance
|
|
313
555
|
this.api.player.getTwitchPlayer = () => {
|
|
314
556
|
return this.twitchPlayer;
|
|
315
557
|
};
|
|
316
558
|
|
|
317
|
-
// Check if live
|
|
318
559
|
this.api.player.isTwitchLive = () => {
|
|
319
560
|
return this.isLive;
|
|
320
561
|
};
|
|
321
562
|
}
|
|
322
563
|
|
|
323
|
-
/**
|
|
324
|
-
* Play
|
|
325
|
-
*/
|
|
326
564
|
play() {
|
|
327
565
|
if (!this.twitchPlayer) {
|
|
328
566
|
return Promise.reject('Player not initialized');
|
|
329
567
|
}
|
|
568
|
+
|
|
330
569
|
this.twitchPlayer.play();
|
|
331
570
|
return Promise.resolve();
|
|
332
571
|
}
|
|
333
572
|
|
|
334
|
-
/**
|
|
335
|
-
* Pause
|
|
336
|
-
*/
|
|
337
573
|
pause() {
|
|
338
574
|
if (!this.twitchPlayer) {
|
|
339
575
|
return Promise.reject('Player not initialized');
|
|
340
576
|
}
|
|
577
|
+
|
|
341
578
|
this.twitchPlayer.pause();
|
|
342
579
|
return Promise.resolve();
|
|
343
580
|
}
|
|
344
581
|
|
|
345
|
-
/**
|
|
346
|
-
* Seek (VODs only)
|
|
347
|
-
*/
|
|
348
582
|
seek(seconds) {
|
|
349
583
|
if (!this.twitchPlayer) {
|
|
350
584
|
return Promise.reject('Player not initialized');
|
|
351
585
|
}
|
|
586
|
+
|
|
352
587
|
if (this.isLive) {
|
|
353
588
|
console.warn('🎮 Twitch Plugin: Cannot seek in live streams');
|
|
354
589
|
return Promise.reject('Cannot seek in live streams');
|
|
355
590
|
}
|
|
591
|
+
|
|
356
592
|
this.twitchPlayer.seek(seconds);
|
|
357
593
|
return Promise.resolve(seconds);
|
|
358
594
|
}
|
|
359
595
|
|
|
360
|
-
/**
|
|
361
|
-
* Get current time
|
|
362
|
-
*/
|
|
363
596
|
getCurrentTime() {
|
|
364
597
|
if (!this.twitchPlayer) {
|
|
365
598
|
return Promise.reject('Player not initialized');
|
|
366
599
|
}
|
|
600
|
+
|
|
367
601
|
return Promise.resolve(this.twitchPlayer.getCurrentTime());
|
|
368
602
|
}
|
|
369
603
|
|
|
370
|
-
/**
|
|
371
|
-
* Get duration (VODs only)
|
|
372
|
-
*/
|
|
373
604
|
getDuration() {
|
|
374
605
|
if (!this.twitchPlayer) {
|
|
375
606
|
return Promise.reject('Player not initialized');
|
|
376
607
|
}
|
|
608
|
+
|
|
377
609
|
if (this.isLive) {
|
|
378
610
|
return Promise.resolve(0);
|
|
379
611
|
}
|
|
612
|
+
|
|
380
613
|
return Promise.resolve(this.twitchPlayer.getDuration());
|
|
381
614
|
}
|
|
382
615
|
|
|
383
|
-
/**
|
|
384
|
-
* Set volume
|
|
385
|
-
*/
|
|
386
616
|
setVolume(volume) {
|
|
387
617
|
if (!this.twitchPlayer) {
|
|
388
618
|
return Promise.reject('Player not initialized');
|
|
389
619
|
}
|
|
620
|
+
|
|
390
621
|
this.twitchPlayer.setVolume(volume);
|
|
391
622
|
return Promise.resolve(volume);
|
|
392
623
|
}
|
|
393
624
|
|
|
394
|
-
/**
|
|
395
|
-
* Get volume
|
|
396
|
-
*/
|
|
397
625
|
getVolume() {
|
|
398
626
|
if (!this.twitchPlayer) {
|
|
399
627
|
return Promise.reject('Player not initialized');
|
|
400
628
|
}
|
|
629
|
+
|
|
401
630
|
return Promise.resolve(this.twitchPlayer.getVolume());
|
|
402
631
|
}
|
|
403
632
|
|
|
404
|
-
/**
|
|
405
|
-
* Mute
|
|
406
|
-
*/
|
|
407
633
|
setMuted(muted) {
|
|
408
634
|
if (!this.twitchPlayer) {
|
|
409
635
|
return Promise.reject('Player not initialized');
|
|
410
636
|
}
|
|
637
|
+
|
|
411
638
|
this.twitchPlayer.setMuted(muted);
|
|
412
639
|
return Promise.resolve(muted);
|
|
413
640
|
}
|
|
414
641
|
|
|
415
|
-
/**
|
|
416
|
-
* Get muted state
|
|
417
|
-
*/
|
|
418
642
|
getMuted() {
|
|
419
643
|
if (!this.twitchPlayer) {
|
|
420
644
|
return Promise.reject('Player not initialized');
|
|
421
645
|
}
|
|
646
|
+
|
|
422
647
|
return Promise.resolve(this.twitchPlayer.getMuted());
|
|
423
648
|
}
|
|
424
649
|
|
|
425
|
-
/**
|
|
426
|
-
* Get quality
|
|
427
|
-
*/
|
|
428
650
|
getQuality() {
|
|
429
651
|
if (!this.twitchPlayer) {
|
|
430
652
|
return Promise.reject('Player not initialized');
|
|
431
653
|
}
|
|
654
|
+
|
|
432
655
|
return Promise.resolve(this.twitchPlayer.getQuality());
|
|
433
656
|
}
|
|
434
657
|
|
|
435
|
-
/**
|
|
436
|
-
* Set quality
|
|
437
|
-
*/
|
|
438
658
|
setQuality(quality) {
|
|
439
659
|
if (!this.twitchPlayer) {
|
|
440
660
|
return Promise.reject('Player not initialized');
|
|
441
661
|
}
|
|
662
|
+
|
|
442
663
|
this.twitchPlayer.setQuality(quality);
|
|
443
664
|
return Promise.resolve(quality);
|
|
444
665
|
}
|
|
445
666
|
|
|
446
|
-
/**
|
|
447
|
-
* Get available qualities
|
|
448
|
-
*/
|
|
449
667
|
getQualities() {
|
|
450
668
|
if (!this.twitchPlayer) {
|
|
451
669
|
return Promise.reject('Player not initialized');
|
|
452
670
|
}
|
|
671
|
+
|
|
453
672
|
return Promise.resolve(this.twitchPlayer.getPlaybackStats().qualitiesAvailable);
|
|
454
673
|
}
|
|
455
674
|
|
|
456
|
-
/**
|
|
457
|
-
* Get channel name
|
|
458
|
-
*/
|
|
459
675
|
getChannel() {
|
|
460
676
|
if (!this.twitchPlayer) {
|
|
461
677
|
return Promise.reject('Player not initialized');
|
|
462
678
|
}
|
|
679
|
+
|
|
463
680
|
return Promise.resolve(this.twitchPlayer.getChannel());
|
|
464
681
|
}
|
|
465
682
|
|
|
466
|
-
/**
|
|
467
|
-
* Get video ID
|
|
468
|
-
*/
|
|
469
683
|
getVideo() {
|
|
470
684
|
if (!this.twitchPlayer) {
|
|
471
685
|
return Promise.reject('Player not initialized');
|
|
472
686
|
}
|
|
687
|
+
|
|
473
688
|
return Promise.resolve(this.twitchPlayer.getVideo());
|
|
474
689
|
}
|
|
475
690
|
|
|
476
|
-
/**
|
|
477
|
-
* Get playback stats
|
|
478
|
-
*/
|
|
479
691
|
getPlaybackStats() {
|
|
480
692
|
if (!this.twitchPlayer) {
|
|
481
693
|
return Promise.reject('Player not initialized');
|
|
482
694
|
}
|
|
695
|
+
|
|
483
696
|
return Promise.resolve(this.twitchPlayer.getPlaybackStats());
|
|
484
697
|
}
|
|
485
698
|
|
|
486
|
-
/**
|
|
487
|
-
* Check if paused
|
|
488
|
-
*/
|
|
489
699
|
isPaused() {
|
|
490
700
|
if (!this.twitchPlayer) {
|
|
491
701
|
return Promise.reject('Player not initialized');
|
|
492
702
|
}
|
|
493
|
-
|
|
703
|
+
|
|
704
|
+
return Promise.resolve(this.isPausedState);
|
|
494
705
|
}
|
|
495
706
|
|
|
496
|
-
/**
|
|
497
|
-
* Load channel (live stream)
|
|
498
|
-
*/
|
|
499
707
|
loadChannel(channel) {
|
|
500
708
|
if (!this.twitchPlayer) {
|
|
501
709
|
return Promise.reject('Player not initialized');
|
|
502
710
|
}
|
|
711
|
+
|
|
503
712
|
this.options.channel = channel;
|
|
504
713
|
this.options.video = null;
|
|
505
714
|
this.isLive = true;
|
|
715
|
+
this.isPausedState = true;
|
|
716
|
+
|
|
506
717
|
this.twitchPlayer.setChannel(channel);
|
|
507
718
|
this.api.triggerEvent('twitchplugin:channelloaded', { channel });
|
|
719
|
+
|
|
508
720
|
return Promise.resolve(channel);
|
|
509
721
|
}
|
|
510
722
|
|
|
511
|
-
/**
|
|
512
|
-
* Load video (VOD)
|
|
513
|
-
*/
|
|
514
723
|
loadVideo(videoId, timestamp = '0h0m0s') {
|
|
515
724
|
if (!this.twitchPlayer) {
|
|
516
725
|
return Promise.reject('Player not initialized');
|
|
517
726
|
}
|
|
727
|
+
|
|
518
728
|
this.options.video = videoId;
|
|
519
729
|
this.options.channel = null;
|
|
520
730
|
this.isLive = false;
|
|
731
|
+
this.isPausedState = true;
|
|
732
|
+
|
|
521
733
|
this.twitchPlayer.setVideo(videoId, timestamp);
|
|
522
734
|
this.api.triggerEvent('twitchplugin:videoloaded', { video: videoId, timestamp });
|
|
735
|
+
|
|
523
736
|
return Promise.resolve(videoId);
|
|
524
737
|
}
|
|
525
738
|
|
|
526
|
-
/**
|
|
527
|
-
* Load collection
|
|
528
|
-
*/
|
|
529
739
|
loadCollection(collectionId, videoId) {
|
|
530
740
|
if (!this.twitchPlayer) {
|
|
531
741
|
return Promise.reject('Player not initialized');
|
|
532
742
|
}
|
|
743
|
+
|
|
533
744
|
this.options.collection = collectionId;
|
|
534
745
|
this.isLive = false;
|
|
746
|
+
this.isPausedState = true;
|
|
747
|
+
|
|
535
748
|
this.twitchPlayer.setCollection(collectionId, videoId);
|
|
536
749
|
this.api.triggerEvent('twitchplugin:collectionloaded', { collection: collectionId, video: videoId });
|
|
750
|
+
|
|
537
751
|
return Promise.resolve(collectionId);
|
|
538
752
|
}
|
|
539
753
|
|
|
540
|
-
/**
|
|
541
|
-
* Dispose plugin
|
|
542
|
-
*/
|
|
543
754
|
dispose() {
|
|
544
|
-
this.api.debug('Disposing
|
|
755
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Disposing');
|
|
756
|
+
|
|
757
|
+
// Clear brand logo hide timer
|
|
758
|
+
if (this.brandLogoHideTimer) {
|
|
759
|
+
clearTimeout(this.brandLogoHideTimer);
|
|
760
|
+
this.brandLogoHideTimer = null;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Remove mouse event listeners for brand logo auto-hide
|
|
764
|
+
if (this.api.container._brandLogoMouseHandler) {
|
|
765
|
+
this.api.container.removeEventListener('mousemove', this.api.container._brandLogoMouseHandler);
|
|
766
|
+
this.api.container.removeEventListener('mouseenter', this.api.container._brandLogoMouseHandler);
|
|
767
|
+
delete this.api.container._brandLogoMouseHandler;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (this.api.container._brandLogoLeaveHandler) {
|
|
771
|
+
this.api.container.removeEventListener('mouseleave', this.api.container._brandLogoLeaveHandler);
|
|
772
|
+
delete this.api.container._brandLogoLeaveHandler;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Restore brand logo to original position in controlbar
|
|
776
|
+
if (this.extractedBrandLogo) {
|
|
777
|
+
// Remove inline styles
|
|
778
|
+
this.extractedBrandLogo.removeAttribute('style');
|
|
779
|
+
|
|
780
|
+
// Restore to original parent
|
|
781
|
+
if (this.extractedBrandLogo._originalParent) {
|
|
782
|
+
this.extractedBrandLogo._originalParent.appendChild(this.extractedBrandLogo);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
this.extractedBrandLogo = null;
|
|
786
|
+
|
|
787
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Brand logo restored');
|
|
788
|
+
}
|
|
545
789
|
|
|
546
790
|
if (this.twitchContainer) {
|
|
547
791
|
this.twitchContainer.remove();
|
|
548
792
|
this.twitchContainer = null;
|
|
549
793
|
}
|
|
550
794
|
|
|
795
|
+
if (this.resizeHandler) {
|
|
796
|
+
window.removeEventListener('resize', this.resizeHandler);
|
|
797
|
+
this.resizeHandler = null;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (this.fullscreenChangeHandler) {
|
|
801
|
+
document.removeEventListener('fullscreenchange', this.fullscreenChangeHandler);
|
|
802
|
+
document.removeEventListener('webkitfullscreenchange', this.fullscreenChangeHandler);
|
|
803
|
+
document.removeEventListener('mozfullscreenchange', this.fullscreenChangeHandler);
|
|
804
|
+
document.removeEventListener('MSFullscreenChange', this.fullscreenChangeHandler);
|
|
805
|
+
this.fullscreenChangeHandler = null;
|
|
806
|
+
}
|
|
807
|
+
|
|
551
808
|
this.twitchPlayer = null;
|
|
809
|
+
this.twitchIframe = null;
|
|
810
|
+
|
|
811
|
+
// Show controls again
|
|
812
|
+
if (this.api.controls) {
|
|
813
|
+
this.api.controls.style.display = '';
|
|
814
|
+
}
|
|
552
815
|
|
|
553
|
-
// Restore native player
|
|
554
816
|
if (this.api.video && this.options.replaceNativePlayer) {
|
|
555
817
|
this.api.video.style.display = '';
|
|
556
818
|
}
|
|
557
819
|
|
|
558
|
-
this.api.debug('Plugin
|
|
820
|
+
if (this.api.player.options.debug) console.log('🎮 Twitch Plugin: Disposed');
|
|
559
821
|
}
|
|
560
822
|
}
|
|
561
823
|
|
|
562
|
-
// Register plugin globally
|
|
563
824
|
if (typeof window.registerMYETVPlugin === 'function') {
|
|
564
825
|
window.registerMYETVPlugin('twitch', TwitchPlugin);
|
|
565
826
|
} else {
|