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 +65 -1
- package/dist/myetv-player.js +469 -20
- package/dist/myetv-player.min.js +427 -8
- package/package.json +3 -2
- package/plugins/google-adsense-ads/README.md +230 -0
- package/plugins/google-adsense-ads/g-adsense-ads-plugin.js +75 -8
- package/plugins/google-ima-ads/README.md +258 -0
- package/plugins/google-ima-ads/g-ima-ads-plugin.js +2 -2
- package/plugins/youtube/myetv-player-youtube-plugin.js +72 -39
package/README.md
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
3
|
# MYETV Audio/Video Player Open Source
|
|
4
4
|
|
|
5
|
+
[](https://github.com/OskarCosimo/myetv-video-player-opensource/actions/workflows/codeql.yml) [](https://github.com/OskarCosimo/myetv-video-player-opensource/actions/workflows/npm-publish.yml) [](https://opensource.org/licenses/MIT)
|
|
6
|
+

|
|
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
|
package/dist/myetv-player.js
CHANGED
|
@@ -3623,7 +3623,7 @@ createControls() {
|
|
|
3623
3623
|
<div class="settings-menu"></div>
|
|
3624
3624
|
</div>
|
|
3625
3625
|
|
|
3626
|
-
|
|
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
|
-
|
|
7388
|
-
|
|
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
|
-
|
|
7392
|
-
this.
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
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
|
|
7400
|
-
this.
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
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
|
-
|
|
7409
|
-
|
|
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('
|
|
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);
|