@@ -72,6 +72,7 @@ class TSDemuxer implements Demuxer {
7272 private aacOverFlow : AudioFrame | null = null ;
7373 private remainderData : Uint8Array | null = null ;
7474 private videoParser : BaseVideoParser | null ;
75+ private videoIntegrityChecker : PacketsIntegrityChecker | null = null ;
7576
7677 constructor (
7778 observer : HlsEventEmitter ,
@@ -182,6 +183,7 @@ class TSDemuxer implements Demuxer {
182183
183184 this . _videoTrack = TSDemuxer . createTrack ( 'video' ) as DemuxedVideoTrack ;
184185 this . _videoTrack . duration = trackDuration ;
186+ this . videoIntegrityChecker = new PacketsIntegrityChecker ( this . logger ) ;
185187 this . _audioTrack = TSDemuxer . createTrack (
186188 'audio' ,
187189 trackDuration ,
@@ -227,6 +229,8 @@ class TSDemuxer implements Demuxer {
227229 let pes : PES | null ;
228230
229231 const videoTrack = this . _videoTrack as DemuxedVideoTrack ;
232+ const videoIntegrityChecker = this
233+ . videoIntegrityChecker as PacketsIntegrityChecker ;
230234 const audioTrack = this . _audioTrack as DemuxedAudioTrack ;
231235 const id3Track = this . _id3Track as DemuxedMetadataTrack ;
232236 const textTrack = this . _txtTrack as DemuxedUserdataTrack ;
@@ -290,7 +294,12 @@ class TSDemuxer implements Demuxer {
290294 switch ( pid ) {
291295 case videoPid :
292296 if ( stt ) {
293- if ( videoData && ( pes = parsePES ( videoData , this . logger ) ) ) {
297+ if (
298+ videoData &&
299+ ( ! videoIntegrityChecker . isCorrupted ||
300+ this . config . handleMpegTsVideoIntegrityErrors === 'process' ) &&
301+ ( pes = parsePES ( videoData , this . logger ) )
302+ ) {
294303 if ( this . videoParser === null ) {
295304 switch ( videoTrack . segmentCodec ) {
296305 case 'avc' :
@@ -309,7 +318,9 @@ class TSDemuxer implements Demuxer {
309318 }
310319
311320 videoData = { data : [ ] , size : 0 } ;
321+ videoIntegrityChecker . reset ( videoPid ) ;
312322 }
323+ videoIntegrityChecker . handle_packet ( data . subarray ( start ) ) ;
313324 if ( videoData ) {
314325 videoData . data . push ( data . subarray ( offset , start + PACKET_LENGTH ) ) ;
315326 videoData . size += start + PACKET_LENGTH - offset ;
@@ -597,6 +608,8 @@ class TSDemuxer implements Demuxer {
597608 this . _id3Track =
598609 this . _txtTrack =
599610 undefined ;
611+
612+ this . videoIntegrityChecker = null ;
600613 }
601614
602615 private parseAACPES ( track : DemuxedAudioTrack , pes : PES ) {
@@ -1057,4 +1070,77 @@ function parsePES(stream: ElementaryStreamData, logger: ILogger): PES | null {
10571070 return null ;
10581071}
10591072
1073+ // See FFMpeg for reference: https://github.com/FFmpeg/FFmpeg/blob/e4c8e80a2efee275f2a10fcf0424c9fc1d86e309/libavformat/mpegts.c#L2811-L2834
1074+ class PacketsIntegrityChecker {
1075+ private readonly logger : ILogger ;
1076+
1077+ private pid : number = 0 ;
1078+ private lastContinuityCounter = - 1 ;
1079+ private integrityState : 'ok' | 'tei-bit' | 'cc-failed' = 'ok' ;
1080+
1081+ constructor ( logger : ILogger ) {
1082+ this . logger = logger ;
1083+ }
1084+
1085+ public get isCorrupted ( ) : boolean {
1086+ return this . integrityState !== 'ok' ;
1087+ }
1088+
1089+ public reset ( pid : number ) {
1090+ this . pid = pid ;
1091+ this . lastContinuityCounter = - 1 ;
1092+ this . integrityState = 'ok' ;
1093+ }
1094+
1095+ public handle_packet ( data : Uint8Array ) {
1096+ if ( data . byteLength < 4 ) {
1097+ return ;
1098+ }
1099+
1100+ const pid = parsePID ( data , 0 ) ;
1101+ if ( pid !== this . pid ) {
1102+ this . logger . debug ( `Packet PID mismatch, expected ${ this . pid } got ${ pid } ` ) ;
1103+ return ;
1104+ }
1105+
1106+ const adaptationFieldControl = ( data [ 3 ] & 0x30 ) >> 4 ;
1107+ if ( adaptationFieldControl === 0 ) {
1108+ return ;
1109+ }
1110+ const continuityCounter = data [ 3 ] & 0xf ;
1111+
1112+ const lastContinuityCounter = this . lastContinuityCounter ;
1113+ this . lastContinuityCounter = continuityCounter ;
1114+
1115+ const hasPayload = ( adaptationFieldControl & 0b01 ) != 0 ;
1116+ const hasAdaptation = ( adaptationFieldControl & 0b10 ) != 0 ;
1117+ const isDiscontinuity =
1118+ hasAdaptation && data [ 4 ] != 0 && ( data [ 5 ] & 0x80 ) != 0 ;
1119+
1120+ if ( isDiscontinuity ) {
1121+ return ;
1122+ }
1123+ if ( lastContinuityCounter < 0 ) {
1124+ return ;
1125+ }
1126+
1127+ const expectedContinuityCounter = hasPayload
1128+ ? ( lastContinuityCounter + 1 ) & 0x0f
1129+ : lastContinuityCounter ;
1130+ if ( continuityCounter !== expectedContinuityCounter ) {
1131+ this . logger . warn (
1132+ `MPEG-TS Continuity Counter check failed for PID='${ pid } ', CC=${ continuityCounter } , Expected-CC=${ expectedContinuityCounter } Last-CC=${ lastContinuityCounter } ` ,
1133+ ) ;
1134+ this . integrityState = 'cc-failed' ;
1135+ return ;
1136+ }
1137+
1138+ if ( ( data [ 1 ] & 0x80 ) !== 0 ) {
1139+ this . logger . warn ( `MPEG-TS Packet had TEI flag set for PID='${ pid } '` ) ;
1140+ this . integrityState = 'tei-bit' ;
1141+ return ;
1142+ }
1143+ }
1144+ }
1145+
10601146export default TSDemuxer ;
0 commit comments