@@ -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 ,
@@ -227,6 +232,7 @@ class TSDemuxer implements Demuxer {
227232 let pes : PES | null ;
228233
229234 const videoTrack = this . _videoTrack as DemuxedVideoTrack ;
235+ const videoIntegrityChecker = this . videoIntegrityChecker ;
230236 const audioTrack = this . _audioTrack as DemuxedAudioTrack ;
231237 const id3Track = this . _id3Track as DemuxedMetadataTrack ;
232238 const textTrack = this . _txtTrack as DemuxedUserdataTrack ;
@@ -290,7 +296,11 @@ class TSDemuxer implements Demuxer {
290296 switch ( pid ) {
291297 case videoPid :
292298 if ( stt ) {
293- if ( videoData && ( pes = parsePES ( videoData , this . logger ) ) ) {
299+ if (
300+ videoData &&
301+ ! videoIntegrityChecker ?. isCorrupted &&
302+ ( pes = parsePES ( videoData , this . logger ) )
303+ ) {
294304 if ( this . videoParser === null ) {
295305 switch ( videoTrack . segmentCodec ) {
296306 case 'avc' :
@@ -309,7 +319,9 @@ class TSDemuxer implements Demuxer {
309319 }
310320
311321 videoData = { data : [ ] , size : 0 } ;
322+ videoIntegrityChecker ?. reset ( videoPid ) ;
312323 }
324+ videoIntegrityChecker ?. handle_packet ( data . subarray ( start ) ) ;
313325 if ( videoData ) {
314326 videoData . data . push ( data . subarray ( offset , start + PACKET_LENGTH ) ) ;
315327 videoData . size += start + PACKET_LENGTH - offset ;
@@ -597,6 +609,8 @@ class TSDemuxer implements Demuxer {
597609 this . _id3Track =
598610 this . _txtTrack =
599611 undefined ;
612+
613+ this . videoIntegrityChecker = null ;
600614 }
601615
602616 private parseAACPES ( track : DemuxedAudioTrack , pes : PES ) {
@@ -1057,4 +1071,77 @@ function parsePES(stream: ElementaryStreamData, logger: ILogger): PES | null {
10571071 return null ;
10581072}
10591073
1074+ // See FFMpeg for reference: https://github.com/FFmpeg/FFmpeg/blob/e4c8e80a2efee275f2a10fcf0424c9fc1d86e309/libavformat/mpegts.c#L2811-L2834
1075+ class PacketsIntegrityChecker {
1076+ private readonly logger : ILogger ;
1077+
1078+ private pid : number = 0 ;
1079+ private lastContinuityCounter = - 1 ;
1080+ private integrityState : 'ok' | 'tei-bit' | 'cc-failed' = 'ok' ;
1081+
1082+ constructor ( logger : ILogger ) {
1083+ this . logger = logger ;
1084+ }
1085+
1086+ public get isCorrupted ( ) : boolean {
1087+ return this . integrityState !== 'ok' ;
1088+ }
1089+
1090+ public reset ( pid : number ) {
1091+ this . pid = pid ;
1092+ this . lastContinuityCounter = - 1 ;
1093+ this . integrityState = 'ok' ;
1094+ }
1095+
1096+ public handle_packet ( data : Uint8Array ) {
1097+ if ( data . byteLength < 4 ) {
1098+ return ;
1099+ }
1100+
1101+ const pid = parsePID ( data , 0 ) ;
1102+ if ( pid !== this . pid ) {
1103+ this . logger . debug ( `Packet PID mismatch, expected ${ this . pid } got ${ pid } ` ) ;
1104+ return ;
1105+ }
1106+
1107+ const adaptationFieldControl = ( data [ 3 ] & 0x30 ) >> 4 ;
1108+ if ( adaptationFieldControl === 0 ) {
1109+ return ;
1110+ }
1111+ const continuityCounter = data [ 3 ] & 0xf ;
1112+
1113+ const lastContinuityCounter = this . lastContinuityCounter ;
1114+ this . lastContinuityCounter = continuityCounter ;
1115+
1116+ const hasPayload = ( adaptationFieldControl & 0b01 ) != 0 ;
1117+ const hasAdaptation = ( adaptationFieldControl & 0b10 ) != 0 ;
1118+ const isDiscontinuity =
1119+ hasAdaptation && data [ 4 ] != 0 && ( data [ 5 ] & 0x80 ) != 0 ;
1120+
1121+ if ( isDiscontinuity ) {
1122+ return ;
1123+ }
1124+ if ( lastContinuityCounter < 0 ) {
1125+ return ;
1126+ }
1127+
1128+ const expectedContinuityCounter = hasPayload
1129+ ? ( lastContinuityCounter + 1 ) & 0x0f
1130+ : lastContinuityCounter ;
1131+ if ( continuityCounter !== expectedContinuityCounter ) {
1132+ this . logger . warn (
1133+ `MPEG-TS Continuity Counter check failed for PID='${ pid } ', CC=${ continuityCounter } , Expected-CC=${ expectedContinuityCounter } Last-CC=${ lastContinuityCounter } ` ,
1134+ ) ;
1135+ this . integrityState = 'cc-failed' ;
1136+ return ;
1137+ }
1138+
1139+ if ( ( data [ 1 ] & 0x80 ) !== 0 ) {
1140+ this . logger . warn ( `MPEG-TS Packet had TEI flag set for PID='${ pid } '` ) ;
1141+ this . integrityState = 'tei-bit' ;
1142+ return ;
1143+ }
1144+ }
1145+ }
1146+
10601147export default TSDemuxer ;
0 commit comments