@@ -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,10 @@ class TSDemuxer implements Demuxer {
182183
183184 this . _videoTrack = TSDemuxer . createTrack ( 'video' ) as DemuxedVideoTrack ;
184185 this . _videoTrack . duration = trackDuration ;
186+ this . videoIntegrityChecker =
187+ this . config . handleMpegTsVideoIntegrityErrors === 'skip'
188+ ? new PacketsIntegrityChecker ( this . logger )
189+ : null ;
185190 this . _audioTrack = TSDemuxer . createTrack (
186191 'audio' ,
187192 trackDuration ,
@@ -228,6 +233,7 @@ class TSDemuxer implements Demuxer {
228233 let pes : PES | null ;
229234
230235 const videoTrack = this . _videoTrack as DemuxedVideoTrack ;
236+ const videoIntegrityChecker = this . videoIntegrityChecker ;
231237 const audioTrack = this . _audioTrack as DemuxedAudioTrack ;
232238 const id3Track = this . _id3Track as DemuxedMetadataTrack ;
233239 const textTrack = this . _txtTrack as DemuxedUserdataTrack ;
@@ -291,15 +297,21 @@ class TSDemuxer implements Demuxer {
291297 switch ( pid ) {
292298 case videoPid :
293299 if ( stt ) {
294- if ( videoData && ( pes = parsePES ( videoData , this . logger ) ) ) {
300+ if (
301+ videoData &&
302+ ! videoIntegrityChecker ?. isCorrupted &&
303+ ( pes = parsePES ( videoData , this . logger ) )
304+ ) {
295305 this . readyVideoParser ( videoTrack . segmentCodec ) ;
296306 if ( this . videoParser !== null ) {
297307 this . videoParser . parsePES ( videoTrack , textTrack , pes , false ) ;
298308 }
299309 }
300310
301311 videoData = { data : [ ] , size : 0 } ;
312+ videoIntegrityChecker ?. reset ( videoPid ) ;
302313 }
314+ videoIntegrityChecker ?. handlePacket ( data . subarray ( start ) ) ;
303315 if ( videoData ) {
304316 videoData . data . push ( data . subarray ( offset , start + PACKET_LENGTH ) ) ;
305317 videoData . size += start + PACKET_LENGTH - offset ;
@@ -464,9 +476,14 @@ class TSDemuxer implements Demuxer {
464476 const videoData = videoTrack . pesData ;
465477 const audioData = audioTrack . pesData ;
466478 const id3Data = id3Track . pesData ;
479+ const videoIntegrityChecker = this . videoIntegrityChecker ;
467480 // try to parse last PES packets
468481 let pes : PES | null ;
469- if ( videoData && ( pes = parsePES ( videoData , this . logger ) ) ) {
482+ if (
483+ videoData &&
484+ ! videoIntegrityChecker ?. isCorrupted &&
485+ ( pes = parsePES ( videoData , this . logger ) )
486+ ) {
470487 this . readyVideoParser ( videoTrack . segmentCodec ) ;
471488 if ( this . videoParser !== null ) {
472489 this . videoParser . parsePES (
@@ -590,6 +607,8 @@ class TSDemuxer implements Demuxer {
590607 this . _id3Track =
591608 this . _txtTrack =
592609 undefined ;
610+
611+ this . videoIntegrityChecker = null ;
593612 }
594613
595614 private parseAACPES ( track : DemuxedAudioTrack , pes : PES ) {
@@ -1050,4 +1069,77 @@ function parsePES(stream: ElementaryStreamData, logger: ILogger): PES | null {
10501069 return null ;
10511070}
10521071
1072+ // See FFMpeg for reference: https://github.com/FFmpeg/FFmpeg/blob/e4c8e80a2efee275f2a10fcf0424c9fc1d86e309/libavformat/mpegts.c#L2811-L2834
1073+ class PacketsIntegrityChecker {
1074+ private readonly logger : ILogger ;
1075+
1076+ private pid : number = 0 ;
1077+ private lastContinuityCounter = - 1 ;
1078+ private integrityState : 'ok' | 'tei-bit' | 'cc-failed' = 'ok' ;
1079+
1080+ constructor ( logger : ILogger ) {
1081+ this . logger = logger ;
1082+ }
1083+
1084+ public get isCorrupted ( ) : boolean {
1085+ return this . integrityState !== 'ok' ;
1086+ }
1087+
1088+ public reset ( pid : number ) {
1089+ this . pid = pid ;
1090+ this . lastContinuityCounter = - 1 ;
1091+ this . integrityState = 'ok' ;
1092+ }
1093+
1094+ public handlePacket ( data : Uint8Array ) {
1095+ if ( data . byteLength < 4 ) {
1096+ return ;
1097+ }
1098+
1099+ const pid = parsePID ( data , 0 ) ;
1100+ if ( pid !== this . pid ) {
1101+ this . logger . debug ( `Packet PID mismatch, expected ${ this . pid } got ${ pid } ` ) ;
1102+ return ;
1103+ }
1104+
1105+ const adaptationFieldControl = ( data [ 3 ] & 0x30 ) >> 4 ;
1106+ if ( adaptationFieldControl === 0 ) {
1107+ return ;
1108+ }
1109+ const continuityCounter = data [ 3 ] & 0xf ;
1110+
1111+ const lastContinuityCounter = this . lastContinuityCounter ;
1112+ this . lastContinuityCounter = continuityCounter ;
1113+
1114+ const hasPayload = ( adaptationFieldControl & 0b01 ) != 0 ;
1115+ const hasAdaptation = ( adaptationFieldControl & 0b10 ) != 0 ;
1116+ const isDiscontinuity =
1117+ hasAdaptation && data [ 4 ] != 0 && ( data [ 5 ] & 0x80 ) != 0 ;
1118+
1119+ if ( isDiscontinuity ) {
1120+ return ;
1121+ }
1122+ if ( lastContinuityCounter < 0 ) {
1123+ return ;
1124+ }
1125+
1126+ const expectedContinuityCounter = hasPayload
1127+ ? ( lastContinuityCounter + 1 ) & 0x0f
1128+ : lastContinuityCounter ;
1129+ if ( continuityCounter !== expectedContinuityCounter ) {
1130+ this . logger . warn (
1131+ `MPEG-TS Continuity Counter check failed for PID='${ pid } ', CC=${ continuityCounter } , Expected-CC=${ expectedContinuityCounter } Last-CC=${ lastContinuityCounter } ` ,
1132+ ) ;
1133+ this . integrityState = 'cc-failed' ;
1134+ return ;
1135+ }
1136+
1137+ if ( ( data [ 1 ] & 0x80 ) !== 0 ) {
1138+ this . logger . warn ( `MPEG-TS Packet had TEI flag set for PID='${ pid } '` ) ;
1139+ this . integrityState = 'tei-bit' ;
1140+ return ;
1141+ }
1142+ }
1143+ }
1144+
10531145export default TSDemuxer ;
0 commit comments