ppvm_player.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. // @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
  2. function setup_playback(description) {
  3. document.getElementById("no-script").classList.remove("noscript");
  4. document.getElementById("controls").classList.add("javascript");
  5. video_manifest = description;
  6. console.log(description);
  7. player = document.getElementById("player");
  8. player.addEventListener("pause", (event) => playStateChanged(false));
  9. player.addEventListener("playing", (event) => playStateChanged(true));
  10. filteredOptions = description.entries.filter(o => shouldOfferStream(o, description.entries));
  11. if(filteredOptions.length == 0){
  12. player.src = "invalid://invalid";
  13. document.getElementById("unplayable-modal").showModal();
  14. return;
  15. }
  16. player.src = filteredOptions[0].path;
  17. qualitySelector = document.getElementById("quality-selector");
  18. filteredOptions.forEach(option => {
  19. opt = document.createElement("option")
  20. opt.value = option.path;
  21. opt.innerHTML = option.label;
  22. qualitySelector.appendChild(opt);
  23. });
  24. startRes = findOptimalOptionForScreen(filteredOptions);
  25. qualitySelector.value = startRes.path
  26. qualitySelected();
  27. doSpeedTest(startRes, filteredOptions);
  28. // Initialize custom video controls
  29. if (typeof CustomVideoControls !== 'undefined') {
  30. CustomVideoControls.init();
  31. }
  32. }
  33. function qualitySelected() {
  34. qualitySelector = document.getElementById("quality-selector");
  35. player = document.getElementById("player");
  36. paused = player.paused;
  37. time = player.currentTime;
  38. player.src = qualitySelector.value;
  39. player.load();
  40. player.currentTime = time;
  41. if(!paused) {
  42. player.play();
  43. }
  44. }
  45. function canPlayStream(stream) {
  46. if(stream.type !== "video") {
  47. return false;
  48. }
  49. player = document.getElementById("player");
  50. return player.canPlayType(`${stream.mimetype}; codecs="${stream.metadata.codecs}"`);
  51. }
  52. function shouldOfferStream(stream, streams) {
  53. if(!canPlayStream(stream)) {
  54. return false;
  55. }
  56. if(stream.metadata.alternative_to == undefined) {
  57. return true;
  58. }
  59. return !hasHigherUsableAlternative(stream, streams);
  60. }
  61. function hasHigherUsableAlternative(stream, streams) {
  62. if(stream.metadata.alternative_to == undefined) {
  63. return false;
  64. }
  65. parent = streams.find(s => s.label == stream.metadata.alternative_to);
  66. if(canPlayStream(parent)) {
  67. return true;
  68. }
  69. return hasHigherUsableAlternative(parent, streams);
  70. }
  71. function findOptimalOptionForScreen(options) {
  72. screenSize = Math.max(window.screen.width, window.screen.height);
  73. screenSize = screenSize * window.devicePixelRatio
  74. filtered = options.filter(o => o.type === "video" && Math.max(o.metadata.size.split("x")[0], o.metadata.size.split("x")[1]) <= screenSize);
  75. console.log(filtered);
  76. if(filtered.length == 0){
  77. return options[options.length - 1];
  78. }
  79. return filtered[0];
  80. }
  81. function downloadVideo() {
  82. document.getElementById("download-modal").showModal();
  83. }
  84. function showInfo() {
  85. document.getElementById("info-modal").showModal();
  86. }
  87. function shareVideo() {
  88. document.getElementById("share-modal").showModal();
  89. }
  90. function playStateChanged(playing) {
  91. controls = document.getElementById("controls")
  92. if(playing) {
  93. controls.classList.remove("paused")
  94. }
  95. else {
  96. controls.classList.add("paused")
  97. }
  98. // Update custom controls if available
  99. if (typeof CustomVideoControls !== 'undefined' && CustomVideoControls.video) {
  100. CustomVideoControls.updatePlayPauseButton();
  101. }
  102. }
  103. function doSpeedTest(currentOption, options) {
  104. startTime = window.performance.now();
  105. request = new XMLHttpRequest();
  106. request.onreadystatechange = function() {
  107. if(request.readyState === 2) {
  108. // Reset start time, headers received.
  109. startTime = window.performance.now();
  110. }
  111. if(request.readyState === 4 && (request.status === 200 || request.status === 206)) {
  112. endTime = window.performance.now();
  113. size = request.response.size;
  114. rate = size / ((endTime - startTime) / 1000)
  115. console.log(`Detected connection rate: ${rate} bytes/sec`);
  116. filtered = options.filter(o => o.type === "video" && o.byterate < rate);
  117. if(filtered.length == 0) {
  118. filtered = [options[options.length - 1]];
  119. }
  120. optimal = findOptimalOptionForScreen(filtered);
  121. if(optimal !== currentOption) {
  122. qualitySelector.value = optimal.path
  123. console.log("Switched res");
  124. qualitySelected();
  125. }
  126. else {
  127. console.log("Got it right first try B)");
  128. }
  129. }
  130. }
  131. requestSize = Math.min(options[0].filesize, Math.round(options[0].byterate));
  132. request.open("GET", options[0].path);
  133. request.responseType = "blob";
  134. request.setRequestHeader("Range", `bytes=0-${requestSize}`);
  135. request.send();
  136. }
  137. /**
  138. * CustomVideoControls object to handle custom video player functionality
  139. */
  140. const CustomVideoControls = {
  141. video: null,
  142. controls: null,
  143. topControls: null,
  144. playPauseBtn: null,
  145. seekSlider: null,
  146. progressBar: null,
  147. progressFilled: null,
  148. progressBuffered: null,
  149. timeDisplay: null,
  150. muteBtn: null,
  151. volumeSlider: null,
  152. fullscreenBtn: null,
  153. controlsTimeout: null,
  154. mouseTimeout: null,
  155. container: null,
  156. isMouseOverContainer: false,
  157. /**
  158. * Initialize the custom video controls
  159. */
  160. init() {
  161. // Get DOM elements
  162. this.video = document.getElementById('player');
  163. this.controls = document.getElementById('custom-controls');
  164. this.topControls = document.getElementById('controls');
  165. this.playPauseBtn = document.getElementById('play-pause-btn');
  166. this.seekSlider = document.getElementById('seek-slider');
  167. this.progressBar = document.getElementById('progress-bar');
  168. this.progressFilled = document.getElementById('progress-filled');
  169. this.progressBuffered = document.getElementById('progress-buffered');
  170. this.timeDisplay = document.getElementById('time-display');
  171. this.muteBtn = document.getElementById('mute-btn');
  172. this.volumeSlider = document.getElementById('volume-slider');
  173. this.fullscreenBtn = document.getElementById('fullscreen-btn');
  174. if (!this.video || !this.controls) {
  175. console.error('Custom video controls: Required elements not found');
  176. return;
  177. }
  178. // Disable default video controls
  179. if (this.video) {
  180. this.video.controls = false;
  181. }
  182. // Remove noscript class from controls
  183. if(this.controls) {
  184. this.controls.classList.remove("noscript");
  185. }
  186. // Create a container for video and controls if it doesn't exist
  187. this.createVideoContainer();
  188. // Set up event listeners
  189. this.setupEventListeners();
  190. // Initialize controls state
  191. this.updatePlayPauseButton();
  192. this.updateVolume();
  193. this.updateTimeDisplay();
  194. // Show controls initially
  195. this.showControls();
  196. // Set up mouse movement detection for auto-hiding controls
  197. this.setupAutoHide();
  198. // Set up mouse inactivity detection
  199. this.setupMouseInactivityDetection();
  200. // Set up focus handling for top controls
  201. this.setupTopControlsFocusHandling();
  202. },
  203. /**
  204. * Create a container wrapper for video and controls for proper fullscreen handling
  205. */
  206. createVideoContainer() {
  207. // Container already exists in HTML, just get reference
  208. this.container = document.getElementById('video-container');
  209. if (!this.container) {
  210. console.error('Video container not found in HTML');
  211. return;
  212. }
  213. },
  214. /**
  215. * Set up all event listeners for the custom controls
  216. */
  217. setupEventListeners() {
  218. // Play/Pause button
  219. this.playPauseBtn.addEventListener('click', () => {
  220. this.togglePlayPause();
  221. this.playPauseBtn.blur();
  222. });
  223. // Seek slider
  224. this.seekSlider.addEventListener('input', () => {
  225. this.seek();
  226. });
  227. this.seekSlider.addEventListener('change', () => {
  228. this.seekSlider.blur();
  229. });
  230. // Progress bar click to seek
  231. this.progressBar.addEventListener('click', (e) => {
  232. this.progressBarSeek(e);
  233. e.target.blur();
  234. });
  235. // Mute button
  236. this.muteBtn.addEventListener('click', () => {
  237. this.toggleMute();
  238. this.muteBtn.blur();
  239. });
  240. // Volume slider
  241. this.volumeSlider.addEventListener('input', () => {
  242. this.updateVolume();
  243. });
  244. this.volumeSlider.addEventListener('change', () => {
  245. this.volumeSlider.blur();
  246. });
  247. // Fullscreen button
  248. this.fullscreenBtn.addEventListener('click', () => {
  249. this.toggleFullscreen();
  250. this.fullscreenBtn.blur();
  251. });
  252. // Video click to toggle play/pause
  253. this.video.addEventListener('click', (e) => {
  254. // Prevent triggering if clicking on controls or if it's a right-click
  255. if (e.target === this.video && e.button === 0) {
  256. this.togglePlayPause();
  257. }
  258. });
  259. // Video events
  260. this.video.addEventListener('timeupdate', () => this.updateProgress());
  261. this.video.addEventListener('loadedmetadata', () => this.updateTimeDisplay());
  262. this.video.addEventListener('volumechange', () => this.updateVolumeDisplay());
  263. this.video.addEventListener('play', () => {
  264. this.updatePlayPauseButton();
  265. this.hideControlsWithTimeout(); // Auto-hide controls when playing
  266. this.hideCursorWithTimeout(); // Auto-hide cursor when playing
  267. });
  268. this.video.addEventListener('pause', () => {
  269. this.updatePlayPauseButton();
  270. this.showControls(); // Show controls when paused
  271. this.showCursor(); // Show cursor when paused
  272. this.resetMouseTimeout(); // Clear cursor timeout when paused
  273. });
  274. this.video.addEventListener('seeking', () => {
  275. this.showControls(); // Show controls when seeking
  276. });
  277. this.video.addEventListener('seeked', () => {
  278. this.hideControlsWithTimeout(); // Auto-hide after seeking
  279. this.hideCursorWithTimeout(); // Auto-hide cursor after seeking
  280. });
  281. // Keyboard controls
  282. document.addEventListener('keydown', (e) => this.handleKeyboard(e));
  283. // Controls visibility
  284. this.controls.addEventListener('mouseenter', () => this.showControls());
  285. this.controls.addEventListener('mouseleave', () => this.hideControls());
  286. this.video.addEventListener('mouseenter', () => this.showControls());
  287. this.video.addEventListener('mouseleave', () => this.hideControls());
  288. this.video.addEventListener('mousemove', () => this.showControls());
  289. // Add mouse movement detection for the entire container
  290. if (this.container) {
  291. this.container.addEventListener('mousemove', () => this.handleMouseMove());
  292. this.container.addEventListener('mouseleave', () => this.handleMouseLeave());
  293. this.container.addEventListener('mouseenter', () => this.handleMouseEnter());
  294. }
  295. // Touch events for mobile support
  296. this.video.addEventListener('touchstart', (e) => {
  297. this.showControls();
  298. // Prevent default to avoid interfering with video controls
  299. if (e.touches.length === 1) {
  300. const touch = e.touches[0];
  301. const rect = this.video.getBoundingClientRect();
  302. // Only toggle play/pause if tapping in the center area
  303. if (touch.clientX > rect.left + rect.width * 0.3 &&
  304. touch.clientX < rect.left + rect.width * 0.7 &&
  305. touch.clientY > rect.top + rect.height * 0.3 &&
  306. touch.clientY < rect.top + rect.height * 0.7) {
  307. this.togglePlayPause();
  308. }
  309. }
  310. });
  311. this.controls.addEventListener('touchstart', () => {
  312. this.showControls();
  313. });
  314. // Handle window resize for responsive behavior
  315. window.addEventListener('resize', () => {
  316. this.handleResize();
  317. });
  318. // Fullscreen change events
  319. document.addEventListener('fullscreenchange', () => this.handleFullscreenChange());
  320. document.addEventListener('webkitfullscreenchange', () => this.handleFullscreenChange());
  321. document.addEventListener('mozfullscreenchange', () => this.handleFullscreenChange());
  322. document.addEventListener('MSFullscreenChange', () => this.handleFullscreenChange());
  323. },
  324. /**
  325. * Toggle between play and pause
  326. */
  327. togglePlayPause() {
  328. if (this.video.paused) {
  329. this.video.play();
  330. } else {
  331. this.video.pause();
  332. }
  333. },
  334. /**
  335. * Update the play/pause button display
  336. */
  337. updatePlayPauseButton() {
  338. if (this.video.paused) {
  339. this.playPauseBtn.textContent = '▶';
  340. this.playPauseBtn.setAttribute('aria-label', 'Play');
  341. } else {
  342. this.playPauseBtn.textContent = '❚❚';
  343. this.playPauseBtn.setAttribute('aria-label', 'Pause');
  344. }
  345. },
  346. /**
  347. * Seek to the position specified by the seek slider
  348. */
  349. seek() {
  350. const seekTime = this.seekSlider.value * this.video.duration / 100;
  351. this.video.currentTime = seekTime;
  352. },
  353. /**
  354. * Handle clicking on the progress bar to seek
  355. */
  356. progressBarSeek(e) {
  357. const rect = this.progressBar.getBoundingClientRect();
  358. const pos = (e.clientX - rect.left) / rect.width;
  359. this.video.currentTime = pos * this.video.duration;
  360. },
  361. /**
  362. * Update the progress bar and time display
  363. */
  364. updateProgress() {
  365. const percentage = (this.video.currentTime / this.video.duration) * 100;
  366. this.progressFilled.style.width = `${percentage}%`;
  367. this.seekSlider.value = percentage;
  368. // Update buffer indicator
  369. if (this.video.buffered.length > 0) {
  370. const bufferedEnd = this.video.buffered.end(this.video.buffered.length - 1);
  371. const bufferedPercentage = (bufferedEnd / this.video.duration) * 100;
  372. this.progressBuffered.style.width = `${bufferedPercentage}%`;
  373. }
  374. this.updateTimeDisplay();
  375. },
  376. /**
  377. * Update the time display
  378. */
  379. updateTimeDisplay() {
  380. const currentTime = this.formatTime(this.video.currentTime);
  381. const duration = this.formatTime(this.video.duration);
  382. this.timeDisplay.textContent = `${currentTime} / ${duration}`;
  383. },
  384. /**
  385. * Format time in seconds to MM:SS or HH:MM:SS format
  386. */
  387. formatTime(seconds) {
  388. if (isNaN(seconds)) return '0:00';
  389. const h = Math.floor(seconds / 3600);
  390. const m = Math.floor((seconds % 3600) / 60);
  391. const s = Math.floor(seconds % 60);
  392. if (h > 0) {
  393. return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
  394. } else {
  395. return `${m}:${s.toString().padStart(2, '0')}`;
  396. }
  397. },
  398. /**
  399. * Toggle mute state
  400. */
  401. toggleMute() {
  402. this.video.muted = !this.video.muted;
  403. },
  404. /**
  405. * Update volume based on slider value
  406. */
  407. updateVolume() {
  408. this.video.volume = this.volumeSlider.value;
  409. },
  410. /**
  411. * Update volume display (mute button)
  412. */
  413. updateVolumeDisplay() {
  414. if (this.video.muted || this.video.volume === 0) {
  415. this.muteBtn.textContent = '🔇';
  416. this.muteBtn.setAttribute('aria-label', 'Unmute');
  417. } else if (this.video.volume < 0.5) {
  418. this.muteBtn.textContent = '🔉';
  419. this.muteBtn.setAttribute('aria-label', 'Mute');
  420. } else {
  421. this.muteBtn.textContent = '🔊';
  422. this.muteBtn.setAttribute('aria-label', 'Mute');
  423. }
  424. },
  425. /**
  426. * Toggle fullscreen mode
  427. */
  428. toggleFullscreen() {
  429. if (!document.fullscreenElement) {
  430. // Request fullscreen for the container instead of just the video
  431. if (this.container.requestFullscreen) {
  432. this.container.requestFullscreen();
  433. } else if (this.container.webkitRequestFullscreen) { /* Safari */
  434. this.container.webkitRequestFullscreen();
  435. } else if (this.container.mozRequestFullScreen) { /* Firefox */
  436. this.container.mozRequestFullScreen();
  437. } else if (this.container.msRequestFullscreen) { /* IE11 */
  438. this.container.msRequestFullscreen();
  439. }
  440. } else {
  441. if (document.exitFullscreen) {
  442. document.exitFullscreen();
  443. } else if (document.webkitExitFullscreen) { /* Safari */
  444. document.webkitExitFullscreen();
  445. } else if (document.mozCancelFullScreen) { /* Firefox */
  446. document.mozCancelFullScreen();
  447. } else if (document.msExitFullscreen) { /* IE11 */
  448. document.msExitFullscreen();
  449. }
  450. }
  451. },
  452. /**
  453. * Handle fullscreen change events
  454. */
  455. handleFullscreenChange() {
  456. const isFullscreen = !!(document.fullscreenElement ||
  457. document.webkitFullscreenElement ||
  458. document.mozFullScreenElement ||
  459. document.msFullscreenElement);
  460. if (isFullscreen) {
  461. // Adjust controls for fullscreen
  462. this.controls.style.position = 'absolute';
  463. this.controls.style.bottom = '0';
  464. this.controls.style.left = '0';
  465. this.controls.style.right = '0';
  466. this.controls.style.zIndex = '2147483647'; // Maximum z-index
  467. // Make video fill the container
  468. this.video.style.width = '100%';
  469. this.video.style.height = '100%';
  470. this.video.style.objectFit = 'contain';
  471. } else {
  472. // Reset styles when exiting fullscreen
  473. this.controls.style.position = '';
  474. this.controls.style.bottom = '';
  475. this.controls.style.left = '';
  476. this.controls.style.right = '';
  477. this.controls.style.zIndex = '';
  478. this.video.style.width = '';
  479. this.video.style.height = '';
  480. this.video.style.objectFit = '';
  481. }
  482. // Always show controls when entering/exiting fullscreen
  483. this.showControls();
  484. // Auto-hide controls and cursor after a delay in fullscreen mode if video is playing
  485. if (isFullscreen && !this.video.paused) {
  486. this.hideControlsWithTimeout();
  487. this.hideCursorWithTimeout();
  488. }
  489. },
  490. /**
  491. * Handle keyboard controls
  492. */
  493. handleKeyboard(e) {
  494. // Only handle keys when video is the active element or no input is focused
  495. if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
  496. return;
  497. }
  498. switch (e.key) {
  499. case ' ':
  500. case 'Spacebar':
  501. e.preventDefault();
  502. this.togglePlayPause();
  503. break;
  504. case 'ArrowLeft':
  505. e.preventDefault();
  506. this.video.currentTime -= 5;
  507. break;
  508. case 'ArrowRight':
  509. e.preventDefault();
  510. this.video.currentTime += 5;
  511. break;
  512. case 'ArrowUp':
  513. e.preventDefault();
  514. this.video.volume = Math.min(1, this.video.volume + 0.1);
  515. this.volumeSlider.value = this.video.volume;
  516. break;
  517. case 'ArrowDown':
  518. e.preventDefault();
  519. this.video.volume = Math.max(0, this.video.volume - 0.1);
  520. this.volumeSlider.value = this.video.volume;
  521. break;
  522. case 'm':
  523. case 'M':
  524. e.preventDefault();
  525. this.toggleMute();
  526. break;
  527. case 'f':
  528. case 'F':
  529. e.preventDefault();
  530. this.toggleFullscreen();
  531. break;
  532. }
  533. },
  534. /**
  535. * Show the controls
  536. */
  537. showControls() {
  538. this.controls.classList.add('show');
  539. if (this.topControls) {
  540. this.topControls.classList.add('show');
  541. }
  542. this.showCursor();
  543. // Only reset timeout if video is playing (to allow auto-hide when idle)
  544. if (!this.video.paused) {
  545. this.resetControlsTimeout();
  546. this.hideControlsWithTimeout();
  547. this.resetMouseTimeout();
  548. this.hideCursorWithTimeout();
  549. }
  550. },
  551. /**
  552. * Hide the controls
  553. */
  554. hideControls() {
  555. if (!this.video.paused) {
  556. this.controls.classList.remove('show');
  557. if (this.topControls) {
  558. this.topControls.classList.remove('show');
  559. }
  560. this.hideCursor();
  561. }
  562. },
  563. /**
  564. * Show the cursor
  565. */
  566. showCursor() {
  567. if (this.container) {
  568. this.container.style.cursor = 'auto';
  569. }
  570. },
  571. /**
  572. * Hide the cursor
  573. */
  574. hideCursor() {
  575. if (this.container && !this.video.paused) {
  576. this.container.style.cursor = 'none';
  577. }
  578. },
  579. /**
  580. * Hide cursor with a timeout
  581. */
  582. hideCursorWithTimeout() {
  583. this.resetMouseTimeout();
  584. this.mouseTimeout = setTimeout(() => {
  585. this.hideCursor();
  586. }, 3000); // 3 seconds
  587. },
  588. /**
  589. * Reset the mouse timeout
  590. */
  591. resetMouseTimeout() {
  592. if (this.mouseTimeout) {
  593. clearTimeout(this.mouseTimeout);
  594. this.mouseTimeout = null;
  595. }
  596. },
  597. /**
  598. * Handle mouse movement inside the container
  599. */
  600. handleMouseMove() {
  601. this.showControls();
  602. if (!this.video.paused) {
  603. this.resetMouseTimeout();
  604. this.hideCursorWithTimeout();
  605. }
  606. },
  607. /**
  608. * Handle mouse entering the container
  609. */
  610. handleMouseEnter() {
  611. this.isMouseOverContainer = true;
  612. this.showControls();
  613. },
  614. /**
  615. * Handle mouse leaving the container
  616. */
  617. handleMouseLeave() {
  618. this.isMouseOverContainer = false;
  619. this.hideControls();
  620. },
  621. /**
  622. * Hide controls with a timeout
  623. */
  624. hideControlsWithTimeout() {
  625. this.resetControlsTimeout();
  626. this.controlsTimeout = setTimeout(() => {
  627. this.hideControls();
  628. }, 3000); // Changed to 3 seconds
  629. },
  630. /**
  631. * Reset the controls timeout
  632. */
  633. resetControlsTimeout() {
  634. if (this.controlsTimeout) {
  635. clearTimeout(this.controlsTimeout);
  636. this.controlsTimeout = null;
  637. }
  638. },
  639. /**
  640. * Set up auto-hide functionality for controls
  641. */
  642. setupAutoHide() {
  643. // Note: These event listeners are already added in setupEventListeners()
  644. // so we don't need to add them again here to prevent duplicate execution
  645. },
  646. /**
  647. * Set up mouse inactivity detection
  648. */
  649. setupMouseInactivityDetection() {
  650. // Initial state - show cursor
  651. this.showCursor();
  652. // Set up initial timeout if video is playing
  653. if (!this.video.paused) {
  654. this.hideCursorWithTimeout();
  655. }
  656. },
  657. /**
  658. * Handle window resize events
  659. */
  660. handleResize() {
  661. // Show controls during resize to ensure they're visible
  662. this.showControls();
  663. // Auto-hide after resize is complete
  664. clearTimeout(this.resizeTimeout);
  665. this.resizeTimeout = setTimeout(() => {
  666. if (!this.video.paused) {
  667. this.hideControlsWithTimeout();
  668. this.hideCursorWithTimeout();
  669. }
  670. }, 1000);
  671. },
  672. /**
  673. * Set up focus handling for top controls
  674. */
  675. setupTopControlsFocusHandling() {
  676. // Get top control buttons and select
  677. const infoBtn = document.querySelector('button[onclick="showInfo()"]');
  678. const shareBtn = document.querySelector('button[onclick="shareVideo()"]');
  679. const downloadBtn = document.querySelector('button[onclick="downloadVideo()"]');
  680. const qualitySelector = document.getElementById('quality-selector');
  681. // Add blur() calls to top control buttons
  682. if (infoBtn) {
  683. infoBtn.addEventListener('click', () => {
  684. infoBtn.blur();
  685. });
  686. }
  687. if (shareBtn) {
  688. shareBtn.addEventListener('click', () => {
  689. shareBtn.blur();
  690. });
  691. }
  692. if (downloadBtn) {
  693. downloadBtn.addEventListener('click', () => {
  694. downloadBtn.blur();
  695. });
  696. }
  697. if (qualitySelector) {
  698. qualitySelector.addEventListener('change', () => {
  699. qualitySelector.blur();
  700. });
  701. }
  702. },
  703. };
  704. // @license-end