mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 19:46:16 -08:00
Merge pull request #801 from markc1984/master
fix: support raw MPEG-TS (.ts) streams in all embedded video players
This commit is contained in:
6 files changed
+146
-11
No files matched your search
@@ -12,6 +12,8 @@ import {
|
||||
} from '@angular/core';
|
||||
import Artplayer from 'artplayer';
|
||||
import Hls from 'hls.js';
|
||||
import { getExtensionFromUrl } from 'm3u-utils';
|
||||
import mpegts from 'mpegts.js';
|
||||
import { Channel } from 'shared-interfaces';
|
||||
|
||||
Artplayer.AUTO_PLAYBACK_TIMEOUT = 10000;
|
||||
@@ -46,6 +48,7 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
|
||||
|
||||
private player!: Artplayer;
|
||||
private hls: Hls | null = null;
|
||||
private mpegtsPlayer: mpegts.Player | null = null;
|
||||
|
||||
private readonly elementRef = inject(ElementRef);
|
||||
|
||||
@@ -65,6 +68,13 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
|
||||
}
|
||||
|
||||
private destroyPlayer(): void {
|
||||
if (this.mpegtsPlayer) {
|
||||
this.mpegtsPlayer.pause();
|
||||
this.mpegtsPlayer.unload();
|
||||
this.mpegtsPlayer.detachMediaElement();
|
||||
this.mpegtsPlayer.destroy();
|
||||
this.mpegtsPlayer = null;
|
||||
}
|
||||
if (this.hls) {
|
||||
this.hls.destroy();
|
||||
this.hls = null;
|
||||
@@ -78,7 +88,8 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
|
||||
const el = this.elementRef.nativeElement.querySelector(
|
||||
'.artplayer-container'
|
||||
);
|
||||
const isLive = this.channel?.url?.toLowerCase().includes('m3u8');
|
||||
const extension = getExtensionFromUrl(this.channel?.url ?? '');
|
||||
const isLive = extension === 'm3u8' || extension === 'ts';
|
||||
|
||||
this.player = new Artplayer({
|
||||
container: el,
|
||||
@@ -118,6 +129,21 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
|
||||
video.src = url;
|
||||
}
|
||||
},
|
||||
ts: (video: HTMLVideoElement, url: string) => {
|
||||
if (mpegts.isSupported()) {
|
||||
if (this.mpegtsPlayer) {
|
||||
this.mpegtsPlayer.destroy();
|
||||
}
|
||||
this.mpegtsPlayer = mpegts.createPlayer({
|
||||
type: 'mpegts',
|
||||
isLive: true,
|
||||
url: url,
|
||||
});
|
||||
this.mpegtsPlayer.attachMediaElement(video);
|
||||
this.mpegtsPlayer.load();
|
||||
this.mpegtsPlayer.play();
|
||||
}
|
||||
},
|
||||
mkv: function (video: HTMLVideoElement, url: string) {
|
||||
video.src = url;
|
||||
video.onerror = () => {
|
||||
@@ -181,7 +207,7 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
|
||||
}
|
||||
|
||||
private getVideoType(url: string): string {
|
||||
const extension = url.split('.').pop()?.toLowerCase();
|
||||
const extension = getExtensionFromUrl(url);
|
||||
switch (extension) {
|
||||
case 'mkv':
|
||||
return 'video/matroska';
|
||||
@@ -189,6 +215,8 @@ export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges {
|
||||
return 'm3u8';
|
||||
case 'mp4':
|
||||
return 'mp4';
|
||||
case 'ts':
|
||||
return 'ts';
|
||||
default:
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import Hls from 'hls.js';
|
||||
import mpegts from 'mpegts.js';
|
||||
import { getExtensionFromUrl } from 'm3u-utils';
|
||||
import { DataService } from 'services';
|
||||
import { Channel } from 'shared-interfaces';
|
||||
@@ -43,6 +44,8 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
/** HLS object */
|
||||
hls!: Hls;
|
||||
/** mpegts.js player for raw MPEG-TS streams */
|
||||
private mpegtsPlayer: mpegts.Player | null = null;
|
||||
|
||||
/** Captions/subtitles indicator */
|
||||
@Input() showCaptions!: boolean;
|
||||
@@ -89,6 +92,13 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
* @param channel given channel object
|
||||
*/
|
||||
playChannel(channel: Channel): void {
|
||||
if (this.mpegtsPlayer) {
|
||||
this.mpegtsPlayer.pause();
|
||||
this.mpegtsPlayer.unload();
|
||||
this.mpegtsPlayer.detachMediaElement();
|
||||
this.mpegtsPlayer.destroy();
|
||||
this.mpegtsPlayer = null;
|
||||
}
|
||||
if (this.hls) this.hls.destroy();
|
||||
if (channel.url) {
|
||||
const url = channel.url + (channel.epgParams ?? '');
|
||||
@@ -102,7 +112,19 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
if (extension === 'ts' && mpegts.isSupported()) {
|
||||
console.log('Using mpegts.js for TS stream:', channel.name, url);
|
||||
this.mpegtsPlayer = mpegts.createPlayer({
|
||||
type: 'mpegts',
|
||||
isLive: true,
|
||||
url: url,
|
||||
});
|
||||
this.mpegtsPlayer.attachMediaElement(
|
||||
this.videoPlayer.nativeElement
|
||||
);
|
||||
this.mpegtsPlayer.load();
|
||||
this.handlePlayOperation();
|
||||
} else if (
|
||||
extension !== 'mp4' &&
|
||||
extension !== 'mpv' &&
|
||||
Hls &&
|
||||
@@ -182,6 +204,13 @@ export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
'volumechange',
|
||||
this.onVolumeChange
|
||||
);
|
||||
if (this.mpegtsPlayer) {
|
||||
this.mpegtsPlayer.pause();
|
||||
this.mpegtsPlayer.unload();
|
||||
this.mpegtsPlayer.detachMediaElement();
|
||||
this.mpegtsPlayer.destroy();
|
||||
this.mpegtsPlayer = null;
|
||||
}
|
||||
if (this.hls) {
|
||||
this.hls.destroy();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import '@yangkghjh/videojs-aspect-ratio-panel';
|
||||
import { getExtensionFromUrl } from 'm3u-utils';
|
||||
import mpegts from 'mpegts.js';
|
||||
import videoJs from 'video.js';
|
||||
import 'videojs-contrib-quality-levels';
|
||||
import 'videojs-quality-selector-hls';
|
||||
@@ -33,6 +35,8 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() options!: any;
|
||||
/** VideoJs object */
|
||||
player!: any;
|
||||
/** mpegts.js player for raw MPEG-TS streams */
|
||||
private mpegtsPlayer: mpegts.Player | null = null;
|
||||
@Input() volume = 1;
|
||||
@Input() startTime = 0;
|
||||
@Output() timeUpdate = new EventEmitter<{
|
||||
@@ -44,12 +48,17 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
* Instantiate Video.js on component init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
const source = this.options?.sources?.[0];
|
||||
const isMpegTs = this.isMpegTsSource(source?.src);
|
||||
|
||||
// For raw MPEG-TS streams, init Video.js without a source (UI/controls only)
|
||||
const vjsOptions = isMpegTs
|
||||
? { ...this.options, sources: [], autoplay: false }
|
||||
: { ...this.options, autoplay: true };
|
||||
|
||||
this.player = videoJs(
|
||||
this.target.nativeElement,
|
||||
{
|
||||
...this.options,
|
||||
autoplay: true,
|
||||
},
|
||||
vjsOptions,
|
||||
() => {
|
||||
console.log(
|
||||
'Setting VideoJS player initial volume to:',
|
||||
@@ -100,6 +109,11 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
duration: this.player.duration(),
|
||||
});
|
||||
});
|
||||
|
||||
// Attach mpegts.js after Video.js is ready
|
||||
if (isMpegTs) {
|
||||
this.initMpegTs(source.src);
|
||||
}
|
||||
}
|
||||
);
|
||||
try {
|
||||
@@ -125,8 +139,16 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
* @param changes contains changed channel object
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['options'].previousValue) {
|
||||
this.player.src(changes['options'].currentValue.sources[0]);
|
||||
if (changes['options']?.previousValue) {
|
||||
const newSource = changes['options'].currentValue.sources[0];
|
||||
if (this.isMpegTsSource(newSource?.src)) {
|
||||
this.destroyMpegTs();
|
||||
this.player.reset();
|
||||
this.initMpegTs(newSource.src);
|
||||
} else {
|
||||
this.destroyMpegTs();
|
||||
this.player.src(newSource);
|
||||
}
|
||||
}
|
||||
if (changes['volume']?.currentValue !== undefined && this.player) {
|
||||
console.log(
|
||||
@@ -141,11 +163,42 @@ export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy {
|
||||
* Removes the players HTML reference on destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.destroyMpegTs();
|
||||
if (this.player) {
|
||||
this.player.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private isMpegTsSource(url?: string): boolean {
|
||||
if (!url) return false;
|
||||
return getExtensionFromUrl(url) === 'ts' && mpegts.isSupported();
|
||||
}
|
||||
|
||||
private initMpegTs(url: string): void {
|
||||
const videoEl = this.player.tech({ IWillNotUseThisInPlugins: true })?.el();
|
||||
if (!videoEl) return;
|
||||
|
||||
console.log('Using mpegts.js for TS stream:', url);
|
||||
this.mpegtsPlayer = mpegts.createPlayer({
|
||||
type: 'mpegts',
|
||||
isLive: true,
|
||||
url: url,
|
||||
});
|
||||
this.mpegtsPlayer.attachMediaElement(videoEl as HTMLVideoElement);
|
||||
this.mpegtsPlayer.load();
|
||||
this.mpegtsPlayer.play();
|
||||
}
|
||||
|
||||
private destroyMpegTs(): void {
|
||||
if (this.mpegtsPlayer) {
|
||||
this.mpegtsPlayer.pause();
|
||||
this.mpegtsPlayer.unload();
|
||||
this.mpegtsPlayer.detachMediaElement();
|
||||
this.mpegtsPlayer.destroy();
|
||||
this.mpegtsPlayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs all available audio tracks for debugging.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user