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,678 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MYETV Player - Gamepad Plugin
|
|
3
|
+
* File: myetv-player-gamepad-remote-plugin.js
|
|
4
|
+
* Adds gamepad/controller support for video playback control
|
|
5
|
+
* Perfect for Smart TVs, gaming consoles, and accessibility
|
|
6
|
+
* Created by https://www.myetv.tv https://oskarcosimo.com
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
(function () {
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
class GamepadPlugin {
|
|
13
|
+
constructor(player, options = {}) {
|
|
14
|
+
this.player = player;
|
|
15
|
+
this.options = {
|
|
16
|
+
// Enable/disable plugin
|
|
17
|
+
enabled: options.enabled !== false,
|
|
18
|
+
|
|
19
|
+
// Preset mappings ('xbox', 'playstation', 'nintendo', 'tv-remote', 'generic')
|
|
20
|
+
preset: options.preset || 'xbox',
|
|
21
|
+
|
|
22
|
+
// Custom mapping (overrides preset if provided)
|
|
23
|
+
customMapping: options.customMapping || null,
|
|
24
|
+
|
|
25
|
+
// Analog stick settings
|
|
26
|
+
deadZone: options.deadZone || 0.2, // Ignore small movements
|
|
27
|
+
seekSensitivity: options.seekSensitivity || 5, // Seconds to seek per input
|
|
28
|
+
volumeSensitivity: options.volumeSensitivity || 0.05, // Volume change per input
|
|
29
|
+
|
|
30
|
+
// Auto-detect controller type
|
|
31
|
+
autoDetect: options.autoDetect !== false,
|
|
32
|
+
|
|
33
|
+
// Polling rate (ms)
|
|
34
|
+
pollingRate: options.pollingRate || 100,
|
|
35
|
+
|
|
36
|
+
// Vibration/haptic feedback
|
|
37
|
+
enableVibration: options.enableVibration !== false,
|
|
38
|
+
vibrationIntensity: options.vibrationIntensity || 0.5,
|
|
39
|
+
|
|
40
|
+
// UI feedback
|
|
41
|
+
showFeedback: options.showFeedback !== false,
|
|
42
|
+
feedbackDuration: options.feedbackDuration || 1000,
|
|
43
|
+
|
|
44
|
+
// Debug mode
|
|
45
|
+
debug: options.debug || false,
|
|
46
|
+
|
|
47
|
+
...options
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
this.connectedGamepads = {};
|
|
51
|
+
this.pollingInterval = null;
|
|
52
|
+
this.lastButtonStates = {};
|
|
53
|
+
this.lastAxisValues = {};
|
|
54
|
+
this.feedbackTimeout = null;
|
|
55
|
+
|
|
56
|
+
// Preset mappings
|
|
57
|
+
this.presetMappings = this.getPresetMappings();
|
|
58
|
+
|
|
59
|
+
// Current mapping
|
|
60
|
+
this.currentMapping = this.options.customMapping ||
|
|
61
|
+
this.presetMappings[this.options.preset] ||
|
|
62
|
+
this.presetMappings.generic;
|
|
63
|
+
|
|
64
|
+
// Get plugin API
|
|
65
|
+
this.api = player.getPluginAPI ? player.getPluginAPI() : {
|
|
66
|
+
player: player,
|
|
67
|
+
video: player.video,
|
|
68
|
+
container: player.container,
|
|
69
|
+
controls: player.controls,
|
|
70
|
+
debug: (msg) => {
|
|
71
|
+
if (this.options.debug) console.log('🎮 Gamepad Plugin:', msg);
|
|
72
|
+
},
|
|
73
|
+
triggerEvent: (event, data) => player.triggerEvent(event, data)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get preset controller mappings
|
|
79
|
+
*/
|
|
80
|
+
getPresetMappings() {
|
|
81
|
+
return {
|
|
82
|
+
// Xbox controller (standard mapping)
|
|
83
|
+
xbox: {
|
|
84
|
+
playPause: { button: 0 }, // A button
|
|
85
|
+
stop: { button: 1 }, // B button
|
|
86
|
+
fullscreen: { button: 2 }, // X button
|
|
87
|
+
mute: { button: 3 }, // Y button
|
|
88
|
+
seekBackward: { button: 4 }, // LB
|
|
89
|
+
seekForward: { button: 5 }, // RB
|
|
90
|
+
volumeDown: { button: 6 }, // LT
|
|
91
|
+
volumeUp: { button: 7 }, // RT
|
|
92
|
+
showInfo: { button: 8 }, // Back/Select
|
|
93
|
+
settings: { button: 9 }, // Start
|
|
94
|
+
seekAxis: { axis: 0 }, // Left stick X
|
|
95
|
+
volumeAxis: { axis: 1 } // Left stick Y
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// PlayStation controller
|
|
99
|
+
playstation: {
|
|
100
|
+
playPause: { button: 0 }, // Cross
|
|
101
|
+
stop: { button: 1 }, // Circle
|
|
102
|
+
fullscreen: { button: 2 }, // Square
|
|
103
|
+
mute: { button: 3 }, // Triangle
|
|
104
|
+
seekBackward: { button: 4 }, // L1
|
|
105
|
+
seekForward: { button: 5 }, // R1
|
|
106
|
+
volumeDown: { button: 6 }, // L2
|
|
107
|
+
volumeUp: { button: 7 }, // R2
|
|
108
|
+
showInfo: { button: 8 }, // Share
|
|
109
|
+
settings: { button: 9 }, // Options
|
|
110
|
+
seekAxis: { axis: 0 }, // Left stick X
|
|
111
|
+
volumeAxis: { axis: 1 } // Left stick Y
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// Nintendo Switch controller
|
|
115
|
+
nintendo: {
|
|
116
|
+
playPause: { button: 1 }, // A (bottom)
|
|
117
|
+
stop: { button: 0 }, // B (right)
|
|
118
|
+
fullscreen: { button: 3 }, // X (top)
|
|
119
|
+
mute: { button: 2 }, // Y (left)
|
|
120
|
+
seekBackward: { button: 4 }, // L
|
|
121
|
+
seekForward: { button: 5 }, // R
|
|
122
|
+
volumeDown: { button: 6 }, // ZL
|
|
123
|
+
volumeUp: { button: 7 }, // ZR
|
|
124
|
+
showInfo: { button: 8 }, // -
|
|
125
|
+
settings: { button: 9 }, // +
|
|
126
|
+
seekAxis: { axis: 0 },
|
|
127
|
+
volumeAxis: { axis: 1 }
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// TV Remote / Generic mapping
|
|
131
|
+
'tv-remote': {
|
|
132
|
+
playPause: { button: 0 }, // OK/Select
|
|
133
|
+
stop: { button: 1 }, // Back
|
|
134
|
+
seekBackward: { button: 4 }, // Left
|
|
135
|
+
seekForward: { button: 5 }, // Right
|
|
136
|
+
volumeDown: { button: 6 }, // Volume -
|
|
137
|
+
volumeUp: { button: 7 }, // Volume +
|
|
138
|
+
mute: { button: 3 }, // Mute
|
|
139
|
+
fullscreen: { button: 2 }, // Guide/Info
|
|
140
|
+
settings: { button: 9 } // Menu
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// Generic fallback
|
|
144
|
+
generic: {
|
|
145
|
+
playPause: { button: 0 },
|
|
146
|
+
stop: { button: 1 },
|
|
147
|
+
fullscreen: { button: 2 },
|
|
148
|
+
mute: { button: 3 },
|
|
149
|
+
seekBackward: { button: 4 },
|
|
150
|
+
seekForward: { button: 5 },
|
|
151
|
+
volumeDown: { button: 6 },
|
|
152
|
+
volumeUp: { button: 7 },
|
|
153
|
+
seekAxis: { axis: 0 },
|
|
154
|
+
volumeAxis: { axis: 1 }
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Setup plugin
|
|
161
|
+
*/
|
|
162
|
+
setup() {
|
|
163
|
+
if (!this.options.enabled) {
|
|
164
|
+
this.api.debug('Plugin disabled');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check Gamepad API support
|
|
169
|
+
if (!navigator.getGamepads) {
|
|
170
|
+
console.warn('🎮 Gamepad API not supported in this browser');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.api.debug('Setup started with preset: ' + this.options.preset);
|
|
175
|
+
|
|
176
|
+
// Setup gamepad event listeners
|
|
177
|
+
this.setupGamepadListeners();
|
|
178
|
+
|
|
179
|
+
// Create feedback UI if enabled
|
|
180
|
+
if (this.options.showFeedback) {
|
|
181
|
+
this.createFeedbackUI();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Add custom methods
|
|
185
|
+
this.addCustomMethods();
|
|
186
|
+
|
|
187
|
+
this.api.debug('Setup completed');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Setup gamepad event listeners
|
|
192
|
+
*/
|
|
193
|
+
setupGamepadListeners() {
|
|
194
|
+
// Gamepad connected
|
|
195
|
+
window.addEventListener('gamepadconnected', (e) => {
|
|
196
|
+
this.onGamepadConnected(e.gamepad);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Gamepad disconnected
|
|
200
|
+
window.addEventListener('gamepaddisconnected', (e) => {
|
|
201
|
+
this.onGamepadDisconnected(e.gamepad);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Check for already connected gamepads
|
|
205
|
+
this.checkExistingGamepads();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Check for already connected gamepads
|
|
210
|
+
*/
|
|
211
|
+
checkExistingGamepads() {
|
|
212
|
+
const gamepads = navigator.getGamepads();
|
|
213
|
+
for (let i = 0; i < gamepads.length; i++) {
|
|
214
|
+
if (gamepads[i]) {
|
|
215
|
+
this.onGamepadConnected(gamepads[i]);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Gamepad connected handler
|
|
222
|
+
*/
|
|
223
|
+
onGamepadConnected(gamepad) {
|
|
224
|
+
this.api.debug('Gamepad connected: ' + gamepad.id + ' (index: ' + gamepad.index + ')');
|
|
225
|
+
|
|
226
|
+
this.connectedGamepads[gamepad.index] = gamepad;
|
|
227
|
+
this.lastButtonStates[gamepad.index] = [];
|
|
228
|
+
this.lastAxisValues[gamepad.index] = [];
|
|
229
|
+
|
|
230
|
+
// Auto-detect controller type
|
|
231
|
+
if (this.options.autoDetect) {
|
|
232
|
+
this.detectControllerType(gamepad);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Start polling if not already started
|
|
236
|
+
if (!this.pollingInterval) {
|
|
237
|
+
this.startPolling();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Show feedback
|
|
241
|
+
this.showFeedback('🎮 Controller Connected: ' + this.getControllerName(gamepad));
|
|
242
|
+
|
|
243
|
+
// Trigger event
|
|
244
|
+
this.api.triggerEvent('gamepad:connected', {
|
|
245
|
+
id: gamepad.id,
|
|
246
|
+
index: gamepad.index,
|
|
247
|
+
buttons: gamepad.buttons.length,
|
|
248
|
+
axes: gamepad.axes.length
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Gamepad disconnected handler
|
|
254
|
+
*/
|
|
255
|
+
onGamepadDisconnected(gamepad) {
|
|
256
|
+
this.api.debug('Gamepad disconnected: ' + gamepad.id);
|
|
257
|
+
|
|
258
|
+
delete this.connectedGamepads[gamepad.index];
|
|
259
|
+
delete this.lastButtonStates[gamepad.index];
|
|
260
|
+
delete this.lastAxisValues[gamepad.index];
|
|
261
|
+
|
|
262
|
+
// Stop polling if no gamepads left
|
|
263
|
+
if (Object.keys(this.connectedGamepads).length === 0) {
|
|
264
|
+
this.stopPolling();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Show feedback
|
|
268
|
+
this.showFeedback('🎮 Controller Disconnected');
|
|
269
|
+
|
|
270
|
+
// Trigger event
|
|
271
|
+
this.api.triggerEvent('gamepad:disconnected', {
|
|
272
|
+
id: gamepad.id,
|
|
273
|
+
index: gamepad.index
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Detect controller type from ID
|
|
279
|
+
*/
|
|
280
|
+
detectControllerType(gamepad) {
|
|
281
|
+
const id = gamepad.id.toLowerCase();
|
|
282
|
+
|
|
283
|
+
if (id.includes('xbox') || id.includes('xinput')) {
|
|
284
|
+
this.api.debug('Detected Xbox controller');
|
|
285
|
+
this.currentMapping = this.presetMappings.xbox;
|
|
286
|
+
} else if (id.includes('playstation') || id.includes('dualshock') || id.includes('dualsense')) {
|
|
287
|
+
this.api.debug('Detected PlayStation controller');
|
|
288
|
+
this.currentMapping = this.presetMappings.playstation;
|
|
289
|
+
} else if (id.includes('nintendo') || id.includes('switch')) {
|
|
290
|
+
this.api.debug('Detected Nintendo controller');
|
|
291
|
+
this.currentMapping = this.presetMappings.nintendo;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get friendly controller name
|
|
297
|
+
*/
|
|
298
|
+
getControllerName(gamepad) {
|
|
299
|
+
const id = gamepad.id.toLowerCase();
|
|
300
|
+
|
|
301
|
+
if (id.includes('xbox')) return 'Xbox Controller';
|
|
302
|
+
if (id.includes('playstation') || id.includes('dualshock')) return 'PlayStation Controller';
|
|
303
|
+
if (id.includes('dualsense')) return 'DualSense Controller';
|
|
304
|
+
if (id.includes('nintendo') || id.includes('switch')) return 'Nintendo Controller';
|
|
305
|
+
|
|
306
|
+
return gamepad.id.substring(0, 30) + (gamepad.id.length > 30 ? '...' : '');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Start polling gamepads
|
|
311
|
+
*/
|
|
312
|
+
startPolling() {
|
|
313
|
+
this.api.debug('Started polling gamepads');
|
|
314
|
+
|
|
315
|
+
this.pollingInterval = setInterval(() => {
|
|
316
|
+
this.pollGamepads();
|
|
317
|
+
}, this.options.pollingRate);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Stop polling gamepads
|
|
322
|
+
*/
|
|
323
|
+
stopPolling() {
|
|
324
|
+
if (this.pollingInterval) {
|
|
325
|
+
clearInterval(this.pollingInterval);
|
|
326
|
+
this.pollingInterval = null;
|
|
327
|
+
this.api.debug('Stopped polling gamepads');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Poll all connected gamepads
|
|
333
|
+
*/
|
|
334
|
+
pollGamepads() {
|
|
335
|
+
const gamepads = navigator.getGamepads();
|
|
336
|
+
|
|
337
|
+
for (let i = 0; i < gamepads.length; i++) {
|
|
338
|
+
const gamepad = gamepads[i];
|
|
339
|
+
if (!gamepad) continue;
|
|
340
|
+
|
|
341
|
+
// Check buttons
|
|
342
|
+
this.checkButtons(gamepad);
|
|
343
|
+
|
|
344
|
+
// Check axes
|
|
345
|
+
this.checkAxes(gamepad);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Check gamepad buttons
|
|
351
|
+
*/
|
|
352
|
+
checkButtons(gamepad) {
|
|
353
|
+
const index = gamepad.index;
|
|
354
|
+
|
|
355
|
+
for (let i = 0; i < gamepad.buttons.length; i++) {
|
|
356
|
+
const button = gamepad.buttons[i];
|
|
357
|
+
const wasPressed = this.lastButtonStates[index][i];
|
|
358
|
+
const isPressed = button.pressed;
|
|
359
|
+
|
|
360
|
+
// Button just pressed (edge detection)
|
|
361
|
+
if (isPressed && !wasPressed) {
|
|
362
|
+
this.handleButtonPress(i, gamepad);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Update state
|
|
366
|
+
this.lastButtonStates[index][i] = isPressed;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Check gamepad axes
|
|
372
|
+
*/
|
|
373
|
+
checkAxes(gamepad) {
|
|
374
|
+
const index = gamepad.index;
|
|
375
|
+
|
|
376
|
+
for (let i = 0; i < gamepad.axes.length; i++) {
|
|
377
|
+
const value = gamepad.axes[i];
|
|
378
|
+
const lastValue = this.lastAxisValues[index][i] || 0;
|
|
379
|
+
|
|
380
|
+
// Check if axis moved beyond dead zone
|
|
381
|
+
if (Math.abs(value) > this.options.deadZone) {
|
|
382
|
+
this.handleAxisMove(i, value, gamepad);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Update state
|
|
386
|
+
this.lastAxisValues[index][i] = value;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Handle button press
|
|
392
|
+
*/
|
|
393
|
+
handleButtonPress(buttonIndex, gamepad) {
|
|
394
|
+
this.api.debug('Button pressed: ' + buttonIndex);
|
|
395
|
+
|
|
396
|
+
// Find action for this button
|
|
397
|
+
for (const [action, mapping] of Object.entries(this.currentMapping)) {
|
|
398
|
+
if (mapping.button === buttonIndex) {
|
|
399
|
+
this.executeAction(action, gamepad);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Handle axis movement
|
|
407
|
+
*/
|
|
408
|
+
handleAxisMove(axisIndex, value, gamepad) {
|
|
409
|
+
// Check seek axis
|
|
410
|
+
if (this.currentMapping.seekAxis && this.currentMapping.seekAxis.axis === axisIndex) {
|
|
411
|
+
if (value > this.options.deadZone) {
|
|
412
|
+
this.executeAction('seekForward', gamepad);
|
|
413
|
+
} else if (value < -this.options.deadZone) {
|
|
414
|
+
this.executeAction('seekBackward', gamepad);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Check volume axis
|
|
419
|
+
if (this.currentMapping.volumeAxis && this.currentMapping.volumeAxis.axis === axisIndex) {
|
|
420
|
+
if (value > this.options.deadZone) {
|
|
421
|
+
this.executeAction('volumeDown', gamepad);
|
|
422
|
+
} else if (value < -this.options.deadZone) {
|
|
423
|
+
this.executeAction('volumeUp', gamepad);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Execute action
|
|
430
|
+
*/
|
|
431
|
+
executeAction(action, gamepad) {
|
|
432
|
+
this.api.debug('Executing action: ' + action);
|
|
433
|
+
|
|
434
|
+
switch (action) {
|
|
435
|
+
case 'playPause':
|
|
436
|
+
this.api.player.togglePlayPause();
|
|
437
|
+
this.showFeedback(this.api.video.paused ? '▶️ Play' : '⏸️ Pause');
|
|
438
|
+
this.vibrate(100);
|
|
439
|
+
break;
|
|
440
|
+
|
|
441
|
+
case 'stop':
|
|
442
|
+
this.api.player.pause();
|
|
443
|
+
this.api.video.currentTime = 0;
|
|
444
|
+
this.showFeedback('⏹️ Stop');
|
|
445
|
+
this.vibrate(100);
|
|
446
|
+
break;
|
|
447
|
+
|
|
448
|
+
case 'seekForward':
|
|
449
|
+
this.api.video.currentTime += this.options.seekSensitivity;
|
|
450
|
+
this.showFeedback('⏩ +' + this.options.seekSensitivity + 's');
|
|
451
|
+
this.vibrate(50);
|
|
452
|
+
break;
|
|
453
|
+
|
|
454
|
+
case 'seekBackward':
|
|
455
|
+
this.api.video.currentTime -= this.options.seekSensitivity;
|
|
456
|
+
this.showFeedback('⏪ -' + this.options.seekSensitivity + 's');
|
|
457
|
+
this.vibrate(50);
|
|
458
|
+
break;
|
|
459
|
+
|
|
460
|
+
case 'volumeUp':
|
|
461
|
+
const newVolumeUp = Math.min(1, this.api.video.volume + this.options.volumeSensitivity);
|
|
462
|
+
this.api.video.volume = newVolumeUp;
|
|
463
|
+
this.showFeedback('🔊 Volume: ' + Math.round(newVolumeUp * 100) + '%');
|
|
464
|
+
this.vibrate(30);
|
|
465
|
+
break;
|
|
466
|
+
|
|
467
|
+
case 'volumeDown':
|
|
468
|
+
const newVolumeDown = Math.max(0, this.api.video.volume - this.options.volumeSensitivity);
|
|
469
|
+
this.api.video.volume = newVolumeDown;
|
|
470
|
+
this.showFeedback('🔉 Volume: ' + Math.round(newVolumeDown * 100) + '%');
|
|
471
|
+
this.vibrate(30);
|
|
472
|
+
break;
|
|
473
|
+
|
|
474
|
+
case 'mute':
|
|
475
|
+
this.api.video.muted = !this.api.video.muted;
|
|
476
|
+
this.showFeedback(this.api.video.muted ? '🔇 Muted' : '🔊 Unmuted');
|
|
477
|
+
this.vibrate(100);
|
|
478
|
+
break;
|
|
479
|
+
|
|
480
|
+
case 'fullscreen':
|
|
481
|
+
this.api.player.toggleFullscreen();
|
|
482
|
+
this.showFeedback(document.fullscreenElement ? '⛶ Fullscreen' : '⛶ Exit Fullscreen');
|
|
483
|
+
this.vibrate(100);
|
|
484
|
+
break;
|
|
485
|
+
|
|
486
|
+
case 'showInfo':
|
|
487
|
+
this.showVideoInfo();
|
|
488
|
+
this.vibrate(50);
|
|
489
|
+
break;
|
|
490
|
+
|
|
491
|
+
case 'settings':
|
|
492
|
+
this.api.triggerEvent('gamepad:settings');
|
|
493
|
+
this.showFeedback('⚙️ Settings');
|
|
494
|
+
this.vibrate(50);
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Trigger custom event
|
|
499
|
+
this.api.triggerEvent('gamepad:action', {
|
|
500
|
+
action: action,
|
|
501
|
+
gamepadIndex: gamepad.index
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Vibrate gamepad (if supported)
|
|
507
|
+
*/
|
|
508
|
+
vibrate(duration = 100) {
|
|
509
|
+
if (!this.options.enableVibration) return;
|
|
510
|
+
|
|
511
|
+
const gamepads = navigator.getGamepads();
|
|
512
|
+
for (let i = 0; i < gamepads.length; i++) {
|
|
513
|
+
const gamepad = gamepads[i];
|
|
514
|
+
if (gamepad && gamepad.vibrationActuator) {
|
|
515
|
+
gamepad.vibrationActuator.playEffect('dual-rumble', {
|
|
516
|
+
duration: duration,
|
|
517
|
+
strongMagnitude: this.options.vibrationIntensity,
|
|
518
|
+
weakMagnitude: this.options.vibrationIntensity * 0.5
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Create feedback UI
|
|
526
|
+
*/
|
|
527
|
+
createFeedbackUI() {
|
|
528
|
+
this.feedbackElement = document.createElement('div');
|
|
529
|
+
this.feedbackElement.className = 'gamepad-feedback';
|
|
530
|
+
this.feedbackElement.style.cssText = `
|
|
531
|
+
position: fixed;
|
|
532
|
+
top: 20px;
|
|
533
|
+
right: 20px;
|
|
534
|
+
background: rgba(0, 0, 0, 0.8);
|
|
535
|
+
color: white;
|
|
536
|
+
padding: 15px 25px;
|
|
537
|
+
border-radius: 8px;
|
|
538
|
+
font-size: 16px;
|
|
539
|
+
font-weight: bold;
|
|
540
|
+
z-index: 10000;
|
|
541
|
+
display: none;
|
|
542
|
+
animation: fadeIn 0.2s;
|
|
543
|
+
`;
|
|
544
|
+
document.body.appendChild(this.feedbackElement);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Show feedback message
|
|
549
|
+
*/
|
|
550
|
+
showFeedback(message) {
|
|
551
|
+
if (!this.options.showFeedback || !this.feedbackElement) return;
|
|
552
|
+
|
|
553
|
+
this.feedbackElement.textContent = message;
|
|
554
|
+
this.feedbackElement.style.display = 'block';
|
|
555
|
+
|
|
556
|
+
// Clear existing timeout
|
|
557
|
+
if (this.feedbackTimeout) {
|
|
558
|
+
clearTimeout(this.feedbackTimeout);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Hide after duration
|
|
562
|
+
this.feedbackTimeout = setTimeout(() => {
|
|
563
|
+
this.feedbackElement.style.display = 'none';
|
|
564
|
+
}, this.options.feedbackDuration);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Show video info
|
|
569
|
+
*/
|
|
570
|
+
showVideoInfo() {
|
|
571
|
+
const currentTime = this.formatTime(this.api.video.currentTime);
|
|
572
|
+
const duration = this.formatTime(this.api.video.duration);
|
|
573
|
+
const volume = Math.round(this.api.video.volume * 100);
|
|
574
|
+
|
|
575
|
+
this.showFeedback(
|
|
576
|
+
`⏱️ ${currentTime} / ${duration}\n` +
|
|
577
|
+
`🔊 Volume: ${volume}%`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Format time
|
|
583
|
+
*/
|
|
584
|
+
formatTime(seconds) {
|
|
585
|
+
const mins = Math.floor(seconds / 60);
|
|
586
|
+
const secs = Math.floor(seconds % 60);
|
|
587
|
+
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Add custom methods to player
|
|
592
|
+
*/
|
|
593
|
+
addCustomMethods() {
|
|
594
|
+
// Get connected gamepads
|
|
595
|
+
this.api.player.getConnectedGamepads = () => {
|
|
596
|
+
return Object.values(this.connectedGamepads);
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// Change mapping preset
|
|
600
|
+
this.api.player.setGamepadPreset = (preset) => {
|
|
601
|
+
return this.setPreset(preset);
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// Set custom mapping
|
|
605
|
+
this.api.player.setGamepadMapping = (mapping) => {
|
|
606
|
+
return this.setCustomMapping(mapping);
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// Get current mapping
|
|
610
|
+
this.api.player.getGamepadMapping = () => {
|
|
611
|
+
return this.currentMapping;
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Set preset
|
|
617
|
+
*/
|
|
618
|
+
setPreset(preset) {
|
|
619
|
+
if (!this.presetMappings[preset]) {
|
|
620
|
+
console.warn('Unknown preset:', preset);
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
this.options.preset = preset;
|
|
625
|
+
this.currentMapping = this.presetMappings[preset];
|
|
626
|
+
this.api.debug('Preset changed to: ' + preset);
|
|
627
|
+
this.showFeedback('🎮 Preset: ' + preset);
|
|
628
|
+
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Set custom mapping
|
|
634
|
+
*/
|
|
635
|
+
setCustomMapping(mapping) {
|
|
636
|
+
this.currentMapping = mapping;
|
|
637
|
+
this.api.debug('Custom mapping applied');
|
|
638
|
+
this.showFeedback('🎮 Custom mapping applied');
|
|
639
|
+
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Dispose plugin
|
|
645
|
+
*/
|
|
646
|
+
dispose() {
|
|
647
|
+
this.api.debug('Disposing plugin');
|
|
648
|
+
|
|
649
|
+
// Stop polling
|
|
650
|
+
this.stopPolling();
|
|
651
|
+
|
|
652
|
+
// Remove feedback UI
|
|
653
|
+
if (this.feedbackElement) {
|
|
654
|
+
this.feedbackElement.remove();
|
|
655
|
+
this.feedbackElement = null;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Clear timeouts
|
|
659
|
+
if (this.feedbackTimeout) {
|
|
660
|
+
clearTimeout(this.feedbackTimeout);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Remove event listeners
|
|
664
|
+
window.removeEventListener('gamepadconnected', this.onGamepadConnected);
|
|
665
|
+
window.removeEventListener('gamepaddisconnected', this.onGamepadDisconnected);
|
|
666
|
+
|
|
667
|
+
this.api.debug('Plugin disposed');
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Register plugin globally
|
|
672
|
+
if (typeof window.registerMYETVPlugin === 'function') {
|
|
673
|
+
window.registerMYETVPlugin('gamepad', GamepadPlugin);
|
|
674
|
+
} else {
|
|
675
|
+
console.error('🎮 MYETV Player plugin system not found');
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|