feat(players): enhance video player with audio track management and UI adjustments

Entire-Checkpoint: f35656857e95
This commit is contained in:
4gray committed 2026-02-21 11:56:21 +01:00
1 parent 95c32708ee
commit 538649feff
5 files changed
+150 -9

No files matched your search

@@ -93,9 +93,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
private readonly store = inject(Store);
/** Active selected channel */
readonly activeChannel$ = this.store
.select(selectActive)
.pipe(filter((channel) => Boolean((channel as Channel)?.url)));
readonly activeChannel$ = this.store.select(selectActive);
/** Channels list */
channels$!: Observable<Channel[]>;
@@ -179,6 +177,7 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
combineLatestWith(this.activatedRoute.queryParams),
switchMap(([params, queryParams]) => {
if (params['id']) {
this.store.dispatch(ChannelActions.resetActiveChannel());
this.store.dispatch(
PlaylistActions.setActivePlaylist({
playlistId: params['id'],
@@ -402,8 +401,9 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
openSettings() {
this.dialog.open(SettingsComponent, {
width: '1000px',
height: '90%',
width: '1200px',
maxWidth: '96vw',
maxHeight: '92vh',
data: { isDialog: true },
});
}
@@ -111,6 +111,7 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
this.hls = new Hls();
this.hls.loadSource(url);
this.hls.attachMedia(video);
this.setupHlsAudioTracks();
} else if (
video.canPlayType('application/vnd.apple.mpegurl')
) {
@@ -119,10 +120,8 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
},
mkv: function (video: HTMLVideoElement, url: string) {
video.src = url;
// Add error handling
video.onerror = () => {
console.error('Error loading MKV file:', video.error);
// Fallback to treating it as a regular video
video.src = url;
};
},
@@ -143,11 +142,49 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
});
}
/**
* Listens for HLS.js audio tracks and adds a settings menu entry
* to ArtPlayer for switching between available audio tracks.
*/
private setupHlsAudioTracks(): void {
if (!this.hls) return;
const hls = this.hls;
hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, () => {
const tracks = hls.audioTracks;
if (!tracks || tracks.length <= 1) return;
this.player.setting.add({
html: 'Audio',
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="22" height="22" fill="white">
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
</svg>`,
width: 220,
tooltip: tracks[hls.audioTrack]?.name || '',
selector: tracks.map((track, index) => ({
html: track.name || track.lang || `Track ${index + 1}`,
default: index === hls.audioTrack,
})),
onSelect: function (this: Artplayer, item: any) {
const selectedIndex = tracks.findIndex(
(t, i) =>
(t.name || t.lang || `Track ${i + 1}`) === item.html
);
if (selectedIndex >= 0) {
hls.audioTrack = selectedIndex;
}
return item.html;
},
} as any);
});
}
private getVideoType(url: string): string {
const extension = url.split('.').pop()?.toLowerCase();
switch (extension) {
case 'mkv':
return 'video/matroska'; // Changed from 'mkv'
return 'video/matroska';
case 'm3u8':
return 'm3u8';
case 'mp4':
@@ -176,6 +176,7 @@ export class ChannelListContainerComponent implements OnInit, OnDestroy {
}
ngOnDestroy(): void {
this.store.dispatch(ChannelActions.resetActiveChannel());
this.store.dispatch(ChannelActions.setChannels({ channels: [] }));
if (this.epgRefreshInterval) {
@@ -13,9 +13,12 @@
}
}
.video-js .vjs-menu-button-popup .vjs-menu {
z-index: 10;
}
@media only screen and (max-width: 599px) {
.video-js {
height: calc(50vh - 65px) !important;
}
}
}
@@ -61,8 +61,34 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
if (this.startTime > 0) {
this.player.currentTime(this.startTime);
}
this.logAudioTracks();
this.setupAudioTrackMenu();
});
// Audio tracks may be added after loadedmetadata (e.g. HLS alternate audio)
const audioTracks = this.player.audioTracks();
if (audioTracks) {
audioTracks.addEventListener('addtrack', () => {
console.log(
'[AudioTrack] addtrack event fired, total tracks:',
this.player.audioTracks().length
);
this.logAudioTracks();
this.setupAudioTrackMenu();
});
audioTracks.addEventListener('removetrack', () => {
console.log(
'[AudioTrack] removetrack event fired, total tracks:',
this.player.audioTracks().length
);
this.logAudioTracks();
this.setupAudioTrackMenu();
});
audioTracks.addEventListener('change', () => {
this.logAudioTracks();
});
}
this.player.on('volumechange', () => {
const currentVolume = this.player.volume();
localStorage.setItem('volume', currentVolume.toString());
@@ -119,4 +145,78 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
this.player.dispose();
}
}
/**
* Logs all available audio tracks for debugging.
*/
private logAudioTracks(): void {
const audioTracks = this.player.audioTracks();
console.log('[AudioTrack] Audio tracks count:', audioTracks?.length ?? 0);
for (let i = 0; i < (audioTracks?.length ?? 0); i++) {
const t = audioTracks[i];
console.log(
`[AudioTrack] Track ${i}: label="${t.label}", language="${t.language}", enabled=${t.enabled}, kind="${t.kind}"`
);
}
// Also check the underlying tech for HLS audio tracks
const tech =
typeof this.player?.tech === 'function'
? this.player.tech({ IWillNotUseThisInPlugins: true })
: null;
const audioMediaGroups =
tech?.vhs?.playlists?.main?.mediaGroups?.AUDIO ??
tech?.vhs?.playlists?.master?.mediaGroups?.AUDIO;
if (audioMediaGroups) {
console.log(
'[AudioTrack] HLS AUDIO media groups:',
JSON.stringify(Object.keys(audioMediaGroups))
);
} else {
console.log(
'[AudioTrack] HLS AUDIO media groups: none found in playlist metadata'
);
}
}
/**
* Sets up the audio track selection menu in the control bar.
* Uses the Video.js audioTracks() API which works with both
* native multi-audio streams and HLS.js alternate audio tracks.
*/
private setupAudioTrackMenu(): void {
const audioTracks = this.player.audioTracks();
console.log(
'[AudioTrack] setupAudioTrackMenu called, tracks:',
audioTracks?.length ?? 0
);
if (!audioTracks || audioTracks.length <= 1) {
console.log(
'[AudioTrack] Skipping menu: need >1 tracks, have',
audioTracks?.length ?? 0
);
console.log(
'[AudioTrack] If VLC/MPV show more tracks, the HLS manifest likely does not expose alternate audio via EXT-X-MEDIA'
);
return;
}
const controlBar = this.player.getChild('controlBar');
if (!controlBar) {
return;
}
let audioButton =
controlBar.getChild('audioTrackButton') ??
controlBar.getChild('AudioTrackButton');
if (!audioButton) {
audioButton = controlBar.addChild('audioTrackButton', {});
}
if (audioButton) {
audioButton.show?.();
audioButton.update?.();
}
}
}