myetv-player 1.5.0 → 1.6.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 CHANGED
@@ -1,13 +1,24 @@
1
- [![CodeQL Advanced](https://github.com/OskarCosimo/myetv-video-player-opensource/actions/workflows/codeql.yml/badge.svg)](https://github.com/OskarCosimo/myetv-video-player-opensource/actions/workflows/codeql.yml) [![Node.js Package](https://github.com/OskarCosimo/myetv-video-player-opensource/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/OskarCosimo/myetv-video-player-opensource/actions/workflows/npm-publish.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
1
+ <div align="center">
2
2
 
3
3
  # MYETV Audio/Video Player Open Source
4
4
 
5
+ [![CodeQL Advanced](https://github.com/OskarCosimo/myetv-video-player-opensource/actions/workflows/codeql.yml/badge.svg)](https://github.com/OskarCosimo/myetv-video-player-opensource/actions/workflows/codeql.yml) [![Node.js Package](https://github.com/OskarCosimo/myetv-video-player-opensource/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/OskarCosimo/myetv-video-player-opensource/actions/workflows/npm-publish.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ ![JavaScript](https://img.shields.io/badge/JavaScript-ES6-F7DF1E?logo=javascript)
7
+
5
8
  A modern and complete HTML5 + JavaScript + css video player with custom controls, multiple quality support, subtitles, Picture-in-Picture and much more.
6
9
 
10
+ [Features](#features) • [Installation](#installation) • [Documentation](#documentation) • [Demo](#demo-page) • [NPM Download](https://www.npmjs.com/package/myetv-player) • [License](#license)
11
+
12
+ </div>
13
+
14
+ ---
15
+ # Documentation
16
+
7
17
  ## Table of Contents
8
18
 
9
19
  - [Features](#features)
10
20
  - [Demo Page](#demo-page)
21
+ - [Screenshot](#screenshot)
11
22
  - [Installation](#installation)
12
23
  - [Basic Usage](#basic-usage)
13
24
  - [Initialization Options](#initialization-options)
@@ -46,6 +57,11 @@ A modern and complete HTML5 + JavaScript + css video player with custom controls
46
57
  ## Demo page
47
58
  [MYETV Video Player Demo Page: https://oskarcosimo.com/myetv-video-player/myetv-player-demo.html](https://oskarcosimo.com/myetv-video-player/myetv-player-demo.html)
48
59
 
60
+ ## Screenshot
61
+
62
+ <img width="1360" height="769" alt="MYETV Video Player Opensource - mouse over" src="https://github.com/user-attachments/assets/71cb5d77-171f-4e14-817c-583df1e137f9" />
63
+ <img width="1360" height="769" alt="MYETV Video Player Opensource - chapters" src="https://github.com/user-attachments/assets/569c3b59-8d5e-4d97-8b9b-67a222e73e06" />
64
+
49
65
  ## Installation
50
66
 
51
67
  ### Include Required Files
@@ -962,6 +978,54 @@ The playlist detection will work through HTML attributes on your video elements:
962
978
  </video>
963
979
  ```
964
980
  ## Adaptive streaming (HLS/DASH)
981
+ (Dash)
982
+ Ecco un esempio per il tuo README.md:​
983
+
984
+ text
985
+ ## Adaptive Streaming (DASH/HLS)
986
+
987
+ MyeTV Player supports adaptive bitrate streaming using DASH (Dynamic Adaptive Streaming over HTTP) and HLS (HTTP Live Streaming) protocols. This enables automatic quality switching based on network conditions for optimal playback experience.
988
+
989
+ ### Features
990
+
991
+ - **Automatic Quality Selection**: The player automatically adjusts video quality based on available bandwidth
992
+ - **Manual Quality Control**: Users can manually select specific resolutions (240p, 360p, 480p, 720p, 1080p, 4K)
993
+ - **Seamless Switching**: Quality changes occur smoothly without interrupting playback
994
+ - **Real-time Monitoring**: Displays current playing quality in the control bar
995
+
996
+ ### Basic Usage
997
+
998
+ #### Method 1: Using HTML Video Tag
999
+ ```
1000
+ <video id="my-player" controls> <source src="https://example.com/video/manifest.mpd" type="application/dash+xml"> <!-- Fallback for browsers without DASH support --> <source src="https://example.com/video/fallback.mp4" type="video/mp4"> </video>
1001
+ <script>const player = new MYETVvideoplayer({ container: '#my-player', autoplay: false, adaptiveStreaming: true, adaptiveQualityControl: true, debug: true });</script>
1002
+ ```
1003
+ #### Method 2: Using JavaScript Load
1004
+ ```
1005
+ const player = new MYETVvideoplayer({
1006
+ container: '#player-container',
1007
+ autoplay: true,
1008
+ debug: true
1009
+ });
1010
+
1011
+ // Load DASH stream
1012
+ player.load({
1013
+ src: 'https://example.com/video/manifest.mpd',
1014
+ type: 'application/dash+xml'
1015
+ });
1016
+
1017
+ // Or load HLS stream
1018
+ player.load({
1019
+ src: 'https://example.com/video/playlist.m3u8',
1020
+ type: 'application/x-mpegURL'
1021
+ });
1022
+ ```
1023
+ The player automatically loads the required libraries:
1024
+
1025
+ - **dash.js** (v5.x) for DASH streams
1026
+ - **hls.js** (latest) for HLS streams
1027
+
1028
+ No additional configuration needed - libraries are loaded on-demand when adaptive streaming is detected.
965
1029
  ### Adaptive Streaming APIs
966
1030
  ```
967
1031
  // Info adaptive streaming
@@ -3623,7 +3623,7 @@ createControls() {
3623
3623
  <div class="settings-menu"></div>
3624
3624
  </div>
3625
3625
 
3626
- ${this.options.showQualitySelector && this.originalSources && this.originalSources.length > 1 ? `
3626
+ ${(this.options.showQualitySelector && this.originalSources && this.originalSources.length > 1) || this.options.adaptiveQualityControl ? `
3627
3627
  <div class="quality-control">
3628
3628
  <button class="control-btn quality-btn" data-tooltip="video_quality">
3629
3629
  <div class="quality-btn-text">
@@ -4494,6 +4494,11 @@ updateQualityDisplay() {
4494
4494
  }
4495
4495
 
4496
4496
  updateQualityButton() {
4497
+ if (this.isAdaptiveStream) {
4498
+ if (this.options.debug) console.log('🔒 Adaptive streaming active - quality button managed by streaming.js');
4499
+ return;
4500
+ }
4501
+
4497
4502
  const qualityBtn = this.controls?.querySelector('.quality-btn');
4498
4503
  if (!qualityBtn) return;
4499
4504
 
@@ -4545,6 +4550,11 @@ updateQualityMenu() {
4545
4550
  const qualityMenu = this.controls?.querySelector('.quality-menu');
4546
4551
  if (!qualityMenu) return;
4547
4552
 
4553
+ if (this.isAdaptiveStream) {
4554
+ if (this.options.debug) console.log('🔒 Adaptive streaming active - quality menu managed by streaming.js');
4555
+ return;
4556
+ }
4557
+
4548
4558
  let menuHTML = '';
4549
4559
 
4550
4560
  // Check if adaptive streaming is active (HLS/DASH)
@@ -7220,6 +7230,16 @@ async loadAdaptiveLibraries() {
7220
7230
  }
7221
7231
 
7222
7232
  try {
7233
+ // Initialize quality selection to Auto BEFORE creating player
7234
+
7235
+ // FORCE Auto mode - always reset at initialization
7236
+ this.selectedQuality = 'auto';
7237
+ this.qualityEventsInitialized = false;
7238
+
7239
+ if (this.options.debug) {
7240
+ console.log('🔍 initializeDash - FORCED selectedQuality to:', this.selectedQuality);
7241
+ }
7242
+
7223
7243
  // Destroy existing DASH player
7224
7244
  if (this.dashPlayer) {
7225
7245
  this.dashPlayer.destroy();
@@ -7384,35 +7404,464 @@ disableDashTextTracks() {
7384
7404
  }
7385
7405
  }
7386
7406
 
7387
- updateAdaptiveQualities() {
7388
- this.adaptiveQualities = [];
7407
+ updateAdaptiveQualities() {
7408
+ this.adaptiveQualities = [];
7409
+
7410
+ if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
7411
+ try {
7412
+ // dash.js 5.x - Get ALL video tracks
7413
+ const videoTracks = this.dashPlayer.getTracksFor('video');
7414
+
7415
+ if (this.options.debug) {
7416
+ console.log('✅ DASH getTracksFor result:', videoTracks);
7417
+ }
7418
+
7419
+ if (videoTracks && videoTracks.length > 0) {
7420
+ // Collect qualities from ALL video tracks
7421
+ const allQualities = [];
7422
+
7423
+ videoTracks.forEach((track, trackIndex) => {
7424
+ const bitrateList = track.bitrateList || [];
7425
+
7426
+ if (this.options.debug) {
7427
+ console.log(`✅ Track ${trackIndex} (${track.codec}):`, bitrateList);
7428
+ }
7429
+
7430
+ bitrateList.forEach((bitrate, index) => {
7431
+ allQualities.push({
7432
+ trackIndex: trackIndex,
7433
+ bitrateIndex: index,
7434
+ label: `${bitrate.height}p`,
7435
+ height: bitrate.height,
7436
+ width: bitrate.width,
7437
+ bandwidth: bitrate.bandwidth,
7438
+ codec: track.codec
7439
+ });
7440
+ });
7441
+ });
7442
+
7443
+ // Sort by height (descending) and remove duplicates
7444
+ const uniqueHeights = [...new Set(allQualities.map(q => q.height))];
7445
+ uniqueHeights.sort((a, b) => b - a);
7446
+
7447
+ this.adaptiveQualities = uniqueHeights.map((height, index) => {
7448
+ const quality = allQualities.find(q => q.height === height);
7449
+ return {
7450
+ index: index,
7451
+ label: `${height}p`,
7452
+ height: height,
7453
+ trackIndex: quality.trackIndex,
7454
+ bitrateIndex: quality.bitrateIndex,
7455
+ bandwidth: quality.bandwidth,
7456
+ codec: quality.codec
7457
+ };
7458
+ });
7459
+
7460
+ if (this.options.debug) {
7461
+ console.log('✅ All DASH qualities merged:', this.adaptiveQualities);
7462
+ }
7463
+ }
7464
+ } catch (error) {
7465
+ if (this.options.debug) {
7466
+ console.error('❌ Error getting DASH qualities:', error);
7467
+ }
7468
+ }
7469
+ } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
7470
+ const levels = this.hlsPlayer.levels;
7471
+ this.adaptiveQualities = levels.map((level, index) => ({
7472
+ index: index,
7473
+ label: this.getQualityLabel(level.height, level.width),
7474
+ height: level.height,
7475
+ bandwidth: level.bitrate
7476
+ }));
7477
+ }
7478
+
7479
+ if (this.options.adaptiveQualityControl) {
7480
+ this.updateAdaptiveQualityMenu();
7481
+ }
7482
+
7483
+ if (this.options.debug) {
7484
+ console.log('📡 Adaptive qualities available:', this.adaptiveQualities);
7485
+ console.log('📡 Selected quality mode:', this.selectedQuality);
7486
+ }
7487
+ }
7488
+
7489
+ updateAdaptiveQualityMenu() {
7490
+ const qualityMenu = this.controls?.querySelector('.quality-menu');
7491
+ if (!qualityMenu) {
7492
+ if (this.options.debug) console.log('❌ Quality menu not found in DOM');
7493
+ return;
7494
+ }
7495
+
7496
+ if (this.adaptiveQualities.length === 0) {
7497
+ if (this.options.debug) console.log('❌ No adaptive qualities to display');
7498
+ return;
7499
+ }
7500
+
7501
+ // Generate menu HTML with "Auto" option
7502
+ const isAutoActive = this.selectedQuality === 'auto';
7503
+ let menuHTML = `<div class="quality-option ${isAutoActive ? 'active' : ''}" data-quality="auto">Auto</div>`;
7504
+
7505
+ // Add all quality options
7506
+ this.adaptiveQualities.forEach((quality) => {
7507
+ const isActive = this.selectedQuality === quality.height;
7508
+
7509
+ if (this.options.debug) {
7510
+ console.log('🔍 Quality item:', quality.label, 'height:', quality.height, 'active:', isActive);
7511
+ }
7512
+
7513
+ menuHTML += `<div class="quality-option ${isActive ? 'active' : ''}" data-quality="${quality.height}">
7514
+ ${quality.label}
7515
+ <span class="quality-playing" style="display: none; color: #4CAF50; margin-left: 8px; font-size: 0.85em;">● Playing</span>
7516
+ </div>`;
7517
+ });
7518
+
7519
+ qualityMenu.innerHTML = menuHTML;
7520
+
7521
+ if (this.options.debug) {
7522
+ console.log('✅ Quality menu populated with', this.adaptiveQualities.length, 'options');
7523
+ }
7524
+
7525
+ // Bind events ONCE
7526
+ if (!this.qualityEventsInitialized) {
7527
+ this.bindAdaptiveQualityEvents();
7528
+ this.qualityEventsInitialized = true;
7529
+ }
7389
7530
 
7531
+ // Update display
7532
+ this.updateAdaptiveQualityDisplay();
7533
+ }
7534
+
7535
+ updateAdaptiveQualityDisplay() {
7536
+ if (!this.dashPlayer && !this.hlsPlayer) return;
7537
+
7538
+ let currentHeight = null;
7539
+
7540
+ try {
7390
7541
  if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
7391
- const bitrates = this.dashPlayer.getBitrateInfoListFor('video');
7392
- this.adaptiveQualities = bitrates.map((bitrate, index) => ({
7393
- index: index,
7394
- label: this.getQualityLabel(bitrate.height, bitrate.width),
7395
- height: bitrate.height,
7396
- bandwidth: bitrate.bandwidth
7397
- }));
7542
+ // Get video element to check actual resolution
7543
+ if (this.video && this.video.videoHeight) {
7544
+ currentHeight = this.video.videoHeight;
7545
+ }
7546
+
7547
+ if (this.options.debug) {
7548
+ console.log('📊 Current video height:', currentHeight, 'Selected mode:', this.selectedQuality);
7549
+ }
7398
7550
  } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
7399
- const levels = this.hlsPlayer.levels;
7400
- this.adaptiveQualities = levels.map((level, index) => ({
7401
- index: index,
7402
- label: this.getQualityLabel(level.height, level.width),
7403
- height: level.height,
7404
- bandwidth: level.bitrate
7405
- }));
7551
+ const currentLevel = this.hlsPlayer.currentLevel;
7552
+ if (currentLevel >= 0 && this.hlsPlayer.levels[currentLevel]) {
7553
+ currentHeight = this.hlsPlayer.levels[currentLevel].height;
7554
+ }
7555
+ }
7556
+
7557
+ // Update button text (top text)
7558
+ const qualityBtnText = this.controls?.querySelector('.quality-btn .selected-quality');
7559
+ if (qualityBtnText) {
7560
+ if (this.selectedQuality === 'auto') {
7561
+ qualityBtnText.textContent = 'Auto';
7562
+ } else {
7563
+ qualityBtnText.textContent = `${this.selectedQuality}p`;
7564
+ }
7565
+ }
7566
+
7567
+ // Update current quality display (bottom text) - ONLY in Auto mode
7568
+ const currentQualityText = this.controls?.querySelector('.quality-btn .current-quality');
7569
+ if (currentQualityText) {
7570
+ if (this.selectedQuality === 'auto' && currentHeight) {
7571
+ currentQualityText.textContent = `${currentHeight}p`;
7572
+ currentQualityText.style.display = 'block';
7573
+ } else {
7574
+ currentQualityText.textContent = '';
7575
+ currentQualityText.style.display = 'none';
7576
+ }
7577
+ }
7578
+
7579
+ // Update menu active states
7580
+ const qualityMenu = this.controls?.querySelector('.quality-menu');
7581
+ if (qualityMenu) {
7582
+ // Remove all active states
7583
+ qualityMenu.querySelectorAll('.quality-option').forEach(opt => {
7584
+ opt.classList.remove('active');
7585
+ });
7586
+
7587
+ // Set active based on selection
7588
+ if (this.selectedQuality === 'auto') {
7589
+ const autoOption = qualityMenu.querySelector('[data-quality="auto"]');
7590
+ if (autoOption) autoOption.classList.add('active');
7591
+ } else {
7592
+ const selectedOption = qualityMenu.querySelector(`[data-quality="${this.selectedQuality}"]`);
7593
+ if (selectedOption) selectedOption.classList.add('active');
7594
+ }
7595
+
7596
+ // Hide all playing indicators
7597
+ qualityMenu.querySelectorAll('.quality-playing').forEach(el => {
7598
+ el.style.display = 'none';
7599
+ });
7600
+
7601
+ // Show playing indicator only in Auto mode
7602
+ if (this.selectedQuality === 'auto' && currentHeight) {
7603
+ const playingOption = qualityMenu.querySelector(`[data-quality="${currentHeight}"] .quality-playing`);
7604
+ if (playingOption) {
7605
+ playingOption.style.display = 'inline';
7606
+ }
7607
+ }
7608
+ }
7609
+
7610
+ } catch (error) {
7611
+ if (this.options.debug) console.error('❌ Error updating quality display:', error);
7612
+ }
7613
+ }
7614
+
7615
+ updateQualityButtonText() {
7616
+ const qualityBtn = this.controls?.querySelector('.quality-btn .selected-quality');
7617
+ if (!qualityBtn) return;
7618
+
7619
+ if (this.selectedQuality === 'auto' || !this.selectedQuality) {
7620
+ qualityBtn.textContent = this.t('auto');
7621
+ } else {
7622
+ const quality = this.adaptiveQualities.find(q => q.index === parseInt(this.selectedQuality));
7623
+ qualityBtn.textContent = quality ? quality.label : 'Auto';
7624
+ }
7625
+ }
7626
+
7627
+ bindAdaptiveQualityEvents() {
7628
+ const qualityMenu = this.controls?.querySelector('.quality-menu');
7629
+ const qualityBtn = this.controls?.querySelector('.quality-btn');
7630
+
7631
+ if (!qualityMenu || !qualityBtn) return;
7632
+
7633
+ // Toggle menu
7634
+ qualityBtn.addEventListener('click', (e) => {
7635
+ e.stopPropagation();
7636
+ qualityMenu.classList.toggle('active');
7637
+
7638
+ // Update display when opening
7639
+ if (qualityMenu.classList.contains('active')) {
7640
+ this.updateAdaptiveQualityDisplay();
7406
7641
  }
7642
+ });
7407
7643
 
7408
- if (this.options.adaptiveQualityControl) {
7409
- this.updateAdaptiveQualityMenu();
7644
+ // Close menu on outside click
7645
+ const closeMenuHandler = (e) => {
7646
+ if (!qualityBtn.contains(e.target) && !qualityMenu.contains(e.target)) {
7647
+ qualityMenu.classList.remove('active');
7410
7648
  }
7649
+ };
7650
+ document.addEventListener('click', closeMenuHandler);
7651
+
7652
+ // Handle quality selection
7653
+ qualityMenu.addEventListener('click', (e) => {
7654
+ const option = e.target.closest('.quality-option');
7655
+ if (!option) return;
7656
+
7657
+ e.stopPropagation();
7658
+
7659
+ const qualityData = option.getAttribute('data-quality');
7411
7660
 
7412
7661
  if (this.options.debug) {
7413
- console.log('📡 Adaptive qualities available:', this.adaptiveQualities);
7662
+ console.log('🎬 Quality clicked - raw data:', qualityData, 'type:', typeof qualityData);
7663
+ }
7664
+
7665
+ if (qualityData === 'auto') {
7666
+ // Enable auto mode
7667
+ this.selectedQuality = 'auto';
7668
+
7669
+ if (this.adaptiveStreamingType === 'dash' && this.dashPlayer) {
7670
+ this.dashPlayer.updateSettings({
7671
+ streaming: {
7672
+ abr: {
7673
+ autoSwitchBitrate: { video: true }
7674
+ }
7675
+ }
7676
+ });
7677
+ if (this.options.debug) console.log('✅ Auto quality enabled');
7678
+ } else if (this.adaptiveStreamingType === 'hls' && this.hlsPlayer) {
7679
+ this.hlsPlayer.currentLevel = -1;
7680
+ }
7681
+
7682
+ } else {
7683
+ // Manual quality selection
7684
+ const selectedHeight = parseInt(qualityData, 10);
7685
+
7686
+ if (isNaN(selectedHeight)) {
7687
+ if (this.options.debug) console.error('❌ Invalid quality data:', qualityData);
7688
+ return;
7689
+ }
7690
+
7691
+ if (this.options.debug) {
7692
+ console.log('🎬 Setting manual quality to height:', selectedHeight);
7693
+ }
7694
+
7695
+ this.selectedQuality = selectedHeight;
7696
+
7697
+ if (this.adaptiveStreamingType === 'dash') {
7698
+ this.setDashQualityByHeight(selectedHeight);
7699
+ } else if (this.adaptiveStreamingType === 'hls') {
7700
+ const levelIndex = this.hlsPlayer.levels.findIndex(l => l.height === selectedHeight);
7701
+ if (levelIndex >= 0) {
7702
+ this.hlsPlayer.currentLevel = levelIndex;
7703
+ }
7704
+ }
7414
7705
  }
7706
+
7707
+ // Update display immediately
7708
+ this.updateAdaptiveQualityDisplay();
7709
+
7710
+ // Close menu
7711
+ qualityMenu.classList.remove('active');
7712
+ });
7713
+
7714
+ if (this.options.debug) {
7715
+ console.log('✅ Quality events bound');
7415
7716
  }
7717
+ }
7718
+
7719
+ setDashQualityByHeight(targetHeight) {
7720
+ if (!this.dashPlayer) return;
7721
+
7722
+ try {
7723
+ const targetQuality = this.adaptiveQualities.find(q => q.height === targetHeight);
7724
+ if (!targetQuality) {
7725
+ if (this.options.debug) console.error('❌ Quality not found for height:', targetHeight);
7726
+ return;
7727
+ }
7728
+
7729
+ if (this.options.debug) {
7730
+ console.log('🎬 Setting quality:', targetQuality);
7731
+ }
7732
+
7733
+ // Disable auto quality
7734
+ this.dashPlayer.updateSettings({
7735
+ streaming: {
7736
+ abr: {
7737
+ autoSwitchBitrate: { video: false }
7738
+ }
7739
+ }
7740
+ });
7741
+
7742
+ // Get current video track
7743
+ const currentTrack = this.dashPlayer.getCurrentTrackFor('video');
7744
+
7745
+ if (!currentTrack) {
7746
+ if (this.options.debug) console.error('❌ No current video track');
7747
+ return;
7748
+ }
7749
+
7750
+ // Find the correct track for this quality
7751
+ const allTracks = this.dashPlayer.getTracksFor('video');
7752
+ let targetTrack = null;
7753
+
7754
+ for (const track of allTracks) {
7755
+ if (track.bitrateList && track.bitrateList[targetQuality.bitrateIndex]) {
7756
+ const bitrate = track.bitrateList[targetQuality.bitrateIndex];
7757
+ if (bitrate.height === targetHeight) {
7758
+ targetTrack = track;
7759
+ break;
7760
+ }
7761
+ }
7762
+ }
7763
+
7764
+ if (!targetTrack) {
7765
+ if (this.options.debug) console.error('❌ Target track not found');
7766
+ return;
7767
+ }
7768
+
7769
+ // Switch track if different
7770
+ if (currentTrack.index !== targetTrack.index) {
7771
+ this.dashPlayer.setCurrentTrack(targetTrack);
7772
+ if (this.options.debug) {
7773
+ console.log('✅ Switched to track:', targetTrack.index);
7774
+ }
7775
+ }
7776
+
7777
+ // Force quality on current track
7778
+ setTimeout(() => {
7779
+ try {
7780
+ // Use the MediaPlayer API to set quality
7781
+ this.dashPlayer.updateSettings({
7782
+ streaming: {
7783
+ abr: {
7784
+ initialBitrate: { video: targetQuality.bandwidth / 1000 },
7785
+ maxBitrate: { video: targetQuality.bandwidth / 1000 },
7786
+ minBitrate: { video: targetQuality.bandwidth / 1000 }
7787
+ }
7788
+ }
7789
+ });
7790
+
7791
+ if (this.options.debug) {
7792
+ console.log('✅ Quality locked to:', targetHeight + 'p', 'bandwidth:', targetQuality.bandwidth);
7793
+ }
7794
+
7795
+ // Update button text immediately
7796
+ const qualityBtnText = this.controls?.querySelector('.quality-btn .selected-quality');
7797
+ if (qualityBtnText) {
7798
+ qualityBtnText.textContent = `${targetHeight}p`;
7799
+ }
7800
+
7801
+ // Force reload of segments at new quality
7802
+ const currentTime = this.video.currentTime;
7803
+ this.dashPlayer.seek(currentTime + 0.1);
7804
+ setTimeout(() => {
7805
+ this.dashPlayer.seek(currentTime);
7806
+ }, 100);
7807
+
7808
+ } catch (innerError) {
7809
+ if (this.options.debug) console.error('❌ Error setting quality:', innerError);
7810
+ }
7811
+ }, 100);
7812
+
7813
+ } catch (error) {
7814
+ if (this.options.debug) console.error('❌ Error in setDashQualityByHeight:', error);
7815
+ }
7816
+ }
7817
+
7818
+ setDashQuality(qualityIndex) {
7819
+ if (!this.dashPlayer) return;
7820
+
7821
+ try {
7822
+ const selectedQuality = this.adaptiveQualities[qualityIndex];
7823
+ if (!selectedQuality) {
7824
+ if (this.options.debug) console.error('❌ Quality not found at index:', qualityIndex);
7825
+ return;
7826
+ }
7827
+
7828
+ if (this.options.debug) {
7829
+ console.log('🎬 Setting DASH quality:', selectedQuality);
7830
+ }
7831
+
7832
+ // Disable auto quality
7833
+ this.dashPlayer.updateSettings({
7834
+ streaming: {
7835
+ abr: {
7836
+ autoSwitchBitrate: { video: false }
7837
+ }
7838
+ }
7839
+ });
7840
+
7841
+ // Set the specific quality using bitrateIndex
7842
+ setTimeout(() => {
7843
+ try {
7844
+ this.dashPlayer.setQualityFor('video', selectedQuality.bitrateIndex);
7845
+
7846
+ if (this.options.debug) {
7847
+ console.log('✅ DASH quality set to bitrateIndex:', selectedQuality.bitrateIndex, 'height:', selectedQuality.height);
7848
+ }
7849
+
7850
+ // Update button text immediately
7851
+ const qualityBtnText = this.controls?.querySelector('.quality-btn .selected-quality');
7852
+ if (qualityBtnText) {
7853
+ qualityBtnText.textContent = selectedQuality.label;
7854
+ }
7855
+
7856
+ } catch (innerError) {
7857
+ if (this.options.debug) console.error('❌ Error setting quality:', innerError);
7858
+ }
7859
+ }, 100);
7860
+
7861
+ } catch (error) {
7862
+ if (this.options.debug) console.error('❌ Error in setDashQuality:', error);
7863
+ }
7864
+ }
7416
7865
 
7417
7866
  handleAdaptiveError(data) {
7418
7867
  if (this.options.debug) console.error('📡 Fatal adaptive streaming error:', data);