UNPKG

@jxstjh/jhvideo

Version:

HTML5 jhvideo base on MPEG2-TS Stream Player

856 lines 39.8 kB
/* * Copyright (C) 2021 magicxqq. All Rights Reserved. * * @author magicxqq <xqq@xqq.im> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); import Log from '../utils/logger'; import MediaInfo from '../core/media-info'; import { IllegalStateException } from '../utils/exception'; import BaseDemuxer from './base-demuxer'; import { PAT, PESData, PESSliceQueue, PMT, StreamType } from './pat-pmt-pes'; import { AVCDecoderConfigurationRecord, H264AnnexBParser, H264NaluAVC1, H264NaluType } from './h264'; import SPSParser from './sps-parser'; import { AACADTSParser, AudioSpecificConfig } from './aac'; import { PESPrivateData, PESPrivateDataDescriptor } from './pes-private-data'; var TSDemuxer = /** @class */ (function (_super) { __extends(TSDemuxer, _super); function TSDemuxer(probe_data, config) { var _this = _super.call(this) || this; _this.TAG = 'TSDemuxer'; _this.first_parse_ = true; _this.media_info_ = new MediaInfo(); _this.timescale_ = 90; _this.duration_ = 0; _this.current_pmt_pid_ = -1; _this.program_pmt_map_ = {}; _this.pes_slice_queues_ = {}; _this.video_metadata_ = { sps: undefined, pps: undefined, sps_details: undefined }; _this.audio_metadata_ = { audio_object_type: undefined, sampling_freq_index: undefined, sampling_frequency: undefined, channel_config: undefined }; _this.aac_last_sample_pts_ = undefined; _this.aac_last_incomplete_data_ = null; _this.has_video_ = false; _this.has_audio_ = false; _this.video_init_segment_dispatched_ = false; _this.audio_init_segment_dispatched_ = false; _this.video_metadata_changed_ = false; _this.audio_metadata_changed_ = false; _this.video_track_ = { type: 'video', id: 1, sequenceNumber: 0, samples: [], length: 0 }; _this.audio_track_ = { type: 'audio', id: 2, sequenceNumber: 0, samples: [], length: 0 }; _this.ts_packet_size_ = probe_data.ts_packet_size; _this.sync_offset_ = probe_data.sync_offset; _this.config_ = config; return _this; } TSDemuxer.prototype.destroy = function () { this.media_info_ = null; this.pes_slice_queues_ = null; this.video_metadata_ = null; this.audio_metadata_ = null; this.aac_last_incomplete_data_ = null; this.video_track_ = null; this.audio_track_ = null; _super.prototype.destroy.call(this); }; TSDemuxer.probe = function (buffer) { var data = new Uint8Array(buffer); var sync_offset = -1; var ts_packet_size = 188; if (data.byteLength <= 3 * ts_packet_size) { Log.e('TSDemuxer', "Probe data ".concat(data.byteLength, " bytes is too few for judging MPEG-TS stream format!")); return { match: false }; } while (sync_offset === -1) { var scan_window = Math.min(1000, data.byteLength - 3 * ts_packet_size); for (var i = 0; i < scan_window;) { // sync_byte should all be 0x47(71) if (data[i] === 0x47 && data[i + ts_packet_size] === 0x47 && data[i + 2 * ts_packet_size] === 0x47) { sync_offset = i; break; } else { i++; } } // find sync_offset failed in previous ts_packet_size if (sync_offset === -1) { if (ts_packet_size === 188) { // try 192 packet size (BDAV, etc.) ts_packet_size = 192; } else if (ts_packet_size === 192) { // try 204 packet size (European DVB, etc.) ts_packet_size = 204; } else { // 192, 204 also failed, exit break; } } } if (sync_offset === -1) { // both 188, 192, 204 failed, Non MPEG-TS return { match: false }; } if (ts_packet_size === 192 && sync_offset >= 4) { Log.v('TSDemuxer', "ts_packet_size = 192, m2ts mode"); sync_offset -= 4; } else if (ts_packet_size === 204) { Log.v('TSDemuxer', "ts_packet_size = 204, RS encoded MPEG2-TS stream"); } return { match: true, consumed: 0, ts_packet_size: ts_packet_size, sync_offset: sync_offset }; }; TSDemuxer.prototype.bindDataSource = function (loader) { loader.onDataArrival = this.parseChunks.bind(this); return this; }; TSDemuxer.prototype.resetMediaInfo = function () { this.media_info_ = new MediaInfo(); }; TSDemuxer.prototype.parseChunks = function (chunk, byte_start) { if (!this.onError || !this.onMediaInfo || !this.onTrackMetadata || !this.onDataAvailable) { throw new IllegalStateException('onError & onMediaInfo & onTrackMetadata & onDataAvailable callback must be specified'); } var offset = 0; if (this.first_parse_) { this.first_parse_ = false; offset = this.sync_offset_; } while (offset + this.ts_packet_size_ <= chunk.byteLength) { var file_position = byte_start + offset; if (this.ts_packet_size_ === 192) { // skip ATS field (2-bits copy-control + 30-bits timestamp) for m2ts offset += 4; } var data = new Uint8Array(chunk, offset, 188); var sync_byte = data[0]; if (sync_byte !== 0x47) { Log.e(this.TAG, "sync_byte = ".concat(sync_byte, ", not 0x47")); break; } var payload_unit_start_indicator = (data[1] & 0x40) >>> 6; var transport_priority = (data[1] & 0x20) >>> 5; var pid = ((data[1] & 0x1F) << 8) | data[2]; var adaptation_field_control = (data[3] & 0x30) >>> 4; var continuity_conunter = (data[3] & 0x0F); var adaptation_field_info = {}; var ts_payload_start_index = 4; if (adaptation_field_control == 0x02 || adaptation_field_control == 0x03) { var adaptation_field_length = data[4]; if (5 + adaptation_field_length === 188) { // TS packet only has adaption field, jump to next offset += 188; if (this.ts_packet_size_ === 204) { // skip parity word (16 bytes) for RS encoded TS offset += 16; } continue; } else { // parse leading adaptation_field if has payload if (adaptation_field_length > 0) { adaptation_field_info = this.parseAdaptationField(chunk, offset + 4, 1 + adaptation_field_length); } ts_payload_start_index = 4 + 1 + adaptation_field_length; } } if (adaptation_field_control == 0x01 || adaptation_field_control == 0x03) { if (pid === 0 || pid === this.current_pmt_pid_) { // PAT(pid === 0) or PMT if (payload_unit_start_indicator) { var pointer_field = data[ts_payload_start_index]; // skip pointer_field and strange data ts_payload_start_index += 1 + pointer_field; } var ts_payload_length = 188 - ts_payload_start_index; if (pid === 0) { this.parsePAT(chunk, offset + ts_payload_start_index, ts_payload_length, { payload_unit_start_indicator: payload_unit_start_indicator, continuity_conunter: continuity_conunter }); } else { this.parsePMT(chunk, offset + ts_payload_start_index, ts_payload_length, { payload_unit_start_indicator: payload_unit_start_indicator, continuity_conunter: continuity_conunter }); } } else if (this.pmt_ != undefined && this.pmt_.pid_stream_type[pid] != undefined) { // PES var ts_payload_length = 188 - ts_payload_start_index; var stream_type = this.pmt_.pid_stream_type[pid]; // process PES only for known common_pids if (pid === this.pmt_.common_pids.h264 || pid === this.pmt_.common_pids.adts_aac || this.pmt_.pes_private_data_pids[pid] === true) { this.handlePESSlice(chunk, offset + ts_payload_start_index, ts_payload_length, { pid: pid, stream_type: stream_type, file_position: file_position, payload_unit_start_indicator: payload_unit_start_indicator, continuity_conunter: continuity_conunter, random_access_indicator: adaptation_field_info.random_access_indicator }); } } } offset += 188; if (this.ts_packet_size_ === 204) { // skip parity word (16 bytes) for RS encoded TS offset += 16; } } // dispatch parsed frames to the remuxer (consumer) this.dispatchAudioVideoMediaSegment(); return offset; // consumed bytes }; TSDemuxer.prototype.parseAdaptationField = function (buffer, offset, length) { var data = new Uint8Array(buffer, offset, length); var adaptation_field_length = data[0]; if (adaptation_field_length > 0) { if (adaptation_field_length > 183) { Log.w(this.TAG, "Illegal adaptation_field_length: ".concat(adaptation_field_length)); return {}; } var discontinuity_indicator = (data[1] & 0x80) >>> 7; var random_access_indicator = (data[1] & 0x40) >>> 6; var elementary_stream_priority_indicator = (data[1] & 0x20) >>> 5; return { discontinuity_indicator: discontinuity_indicator, random_access_indicator: random_access_indicator, elementary_stream_priority_indicator: elementary_stream_priority_indicator }; } return {}; }; TSDemuxer.prototype.parsePAT = function (buffer, offset, length, misc) { var data = new Uint8Array(buffer, offset, length); var table_id = data[0]; if (table_id !== 0x00) { Log.e(this.TAG, "parsePAT: table_id ".concat(table_id, " is not corresponded to PAT!")); return; } var section_length = ((data[1] & 0x0F) << 8) | data[2]; var transport_stream_id = (data[3] << 8) | data[4]; var version_number = (data[5] & 0x3E) >>> 1; var current_next_indicator = data[5] & 0x01; var section_number = data[6]; var last_section_number = data[7]; var pat = null; if (current_next_indicator === 1 && section_number === 0) { pat = new PAT(); pat.version_number = version_number; } else { pat = this.pat_; if (pat == undefined) { return; } } var program_start_index = 8; var program_bytes = section_length - 5 - 4; // section_length - (headers + crc) var first_program_number = -1; var first_pmt_pid = -1; for (var i = program_start_index; i < program_start_index + program_bytes; i += 4) { var program_number = (data[i] << 8) | data[i + 1]; var pid = ((data[i + 2] & 0x1F) << 8) | data[i + 3]; if (program_number === 0) { // network_PID pat.network_pid = pid; } else { // program_map_PID pat.program_pmt_pid[program_number] = pid; if (first_program_number === -1) { first_program_number = program_number; } if (first_pmt_pid === -1) { first_pmt_pid = pid; } } } // Currently we only deal with first appeared PMT pid if (current_next_indicator === 1 && section_number === 0) { if (this.pat_ == undefined) { Log.v(this.TAG, "Parsed first PAT: ".concat(JSON.stringify(pat))); } this.pat_ = pat; this.current_program_ = first_program_number; this.current_pmt_pid_ = first_pmt_pid; } }; TSDemuxer.prototype.parsePMT = function (buffer, offset, length, misc) { var data = new Uint8Array(buffer, offset, length); var table_id = data[0]; if (table_id !== 0x02) { Log.e(this.TAG, "parsePMT: table_id ".concat(table_id, " is not corresponded to PMT!")); return; } var section_length = ((data[1] & 0x0F) << 8) | data[2]; var program_number = (data[3] << 8) | data[4]; var version_number = (data[5] & 0x3E) >>> 1; var current_next_indicator = data[5] & 0x01; var section_number = data[6]; var last_section_number = data[7]; var pmt = null; if (current_next_indicator === 1 && section_number === 0) { pmt = new PMT(); pmt.program_number = program_number; pmt.version_number = version_number; this.program_pmt_map_[program_number] = pmt; } else { pmt = this.program_pmt_map_[program_number]; if (pmt == undefined) { return; } } var PCR_PID = ((data[8] & 0x1F) << 8) | data[9]; var program_info_length = ((data[10] & 0x0F) << 8) | data[11]; var info_start_index = 12 + program_info_length; var info_bytes = section_length - 9 - program_info_length - 4; for (var i = info_start_index; i < info_start_index + info_bytes;) { var stream_type = data[i]; var elementary_PID = ((data[i + 1] & 0x1F) << 8) | data[i + 2]; var ES_info_length = ((data[i + 3] & 0x0F) << 8) | data[i + 4]; pmt.pid_stream_type[elementary_PID] = stream_type; if (stream_type === StreamType.kH264 && !pmt.common_pids.h264) { pmt.common_pids.h264 = elementary_PID; } else if (stream_type === StreamType.kADTSAAC && !pmt.common_pids.adts_aac) { pmt.common_pids.adts_aac = elementary_PID; } else if (stream_type === StreamType.kPESPrivateData) { pmt.pes_private_data_pids[elementary_PID] = true; if (ES_info_length > 0) { // provide descriptor for PES private data via callback var descriptor = data.subarray(i + 5, i + 5 + ES_info_length); this.dispatchPESPrivateDataDescriptor(elementary_PID, stream_type, descriptor); } } i += 5 + ES_info_length; } if (program_number === this.current_program_) { if (this.pmt_ == undefined) { Log.v(this.TAG, "Parsed first PMT: ".concat(JSON.stringify(pmt))); } this.pmt_ = pmt; if (pmt.common_pids.h264) { this.has_video_ = true; } if (pmt.common_pids.adts_aac) { this.has_audio_ = true; } } }; TSDemuxer.prototype.handlePESSlice = function (buffer, offset, length, misc) { var data = new Uint8Array(buffer, offset, length); var packet_start_code_prefix = (data[0] << 16) | (data[1] << 8) | (data[2]); var stream_id = data[3]; var PES_packet_length = (data[4] << 8) | data[5]; if (misc.payload_unit_start_indicator) { if (packet_start_code_prefix !== 1) { Log.e(this.TAG, "handlePESSlice: packet_start_code_prefix should be 1 but with value ".concat(packet_start_code_prefix)); return; } // handle queued PES slices: // Merge into a big Uint8Array then call parsePES() var slice_queue_1 = this.pes_slice_queues_[misc.pid]; if (slice_queue_1) { var data_1 = new Uint8Array(slice_queue_1.total_length); for (var i = 0, offset_1 = 0; i < slice_queue_1.slices.length; i++) { var slice = slice_queue_1.slices[i]; data_1.set(slice, offset_1); offset_1 += slice.byteLength; } slice_queue_1.slices = []; slice_queue_1.total_length = 0; var pes_data = new PESData(); pes_data.pid = misc.pid; pes_data.data = data_1; pes_data.stream_type = misc.stream_type; pes_data.file_position = slice_queue_1.file_position; pes_data.random_access_indicator = slice_queue_1.random_access_indicator; this.parsePES(pes_data); } // Make a new PES queue for new PES slices this.pes_slice_queues_[misc.pid] = new PESSliceQueue(); this.pes_slice_queues_[misc.pid].file_position = misc.file_position; this.pes_slice_queues_[misc.pid].random_access_indicator = misc.random_access_indicator; } if (this.pes_slice_queues_[misc.pid] == undefined) { // ignore PES slices without [PES slice that has payload_unit_start_indicator] return; } // push subsequent PES slices into pes_queue var slice_queue = this.pes_slice_queues_[misc.pid]; slice_queue.slices.push(data); slice_queue.total_length += data.byteLength; }; TSDemuxer.prototype.parsePES = function (pes_data) { var data = pes_data.data; var packet_start_code_prefix = (data[0] << 16) | (data[1] << 8) | (data[2]); var stream_id = data[3]; var PES_packet_length = (data[4] << 8) | data[5]; if (packet_start_code_prefix !== 1) { Log.e(this.TAG, "parsePES: packet_start_code_prefix should be 1 but with value ".concat(packet_start_code_prefix)); return; } if (stream_id !== 0xBC // program_stream_map && stream_id !== 0xBE // padding_stream && stream_id !== 0xBF // private_stream_2 && stream_id !== 0xF0 // ECM && stream_id !== 0xF1 // EMM && stream_id !== 0xFF // program_stream_directory && stream_id !== 0xF2 // DSMCC && stream_id !== 0xF8) { var PES_scrambling_control = (data[6] & 0x30) >>> 4; var PTS_DTS_flags = (data[7] & 0xC0) >>> 6; var PES_header_data_length = data[8]; var pts = void 0; var dts = void 0; if (PTS_DTS_flags === 0x02 || PTS_DTS_flags === 0x03) { pts = (data[9] & 0x0E) * 536870912 + // 1 << 29 (data[10] & 0xFF) * 4194304 + // 1 << 22 (data[11] & 0xFE) * 16384 + // 1 << 14 (data[12] & 0xFF) * 128 + // 1 << 7 (data[13] & 0xFE) / 2; if (PTS_DTS_flags === 0x03) { dts = (data[14] & 0x0E) * 536870912 + // 1 << 29 (data[15] & 0xFF) * 4194304 + // 1 << 22 (data[16] & 0xFE) * 16384 + // 1 << 14 (data[17] & 0xFF) * 128 + // 1 << 7 (data[18] & 0xFE) / 2; } else { dts = pts; } } var payload_start_index = 6 + 3 + PES_header_data_length; var payload_length = void 0; if (PES_packet_length !== 0) { if (PES_packet_length < 3 + PES_header_data_length) { Log.v(this.TAG, "Malformed PES: PES_packet_length < 3 + PES_header_data_length"); return; } payload_length = PES_packet_length - 3 - PES_header_data_length; } else { // PES_packet_length === 0 payload_length = data.byteLength - payload_start_index; } var payload = data.subarray(payload_start_index, payload_start_index + payload_length); switch (pes_data.stream_type) { case StreamType.kMPEG1Audio: case StreamType.kMPEG2Audio: break; case StreamType.kPESPrivateData: this.parsePESPrivateDataPayload(payload, pts, dts, pes_data.pid, stream_id); break; case StreamType.kADTSAAC: this.parseAACPayload(payload, pts); break; case StreamType.kID3: break; case StreamType.kH264: this.parseH264Payload(payload, pts, dts, pes_data.file_position, pes_data.random_access_indicator); break; case StreamType.kH265: default: break; } } else if (stream_id === 0xBC // program_stream_map || stream_id === 0xBF // private_stream_2 || stream_id === 0xF0 // ECM || stream_id === 0xF1 // EMM || stream_id === 0xFF // program_stream_directory || stream_id === 0xF2 // DSMCC_stream || stream_id === 0xF8) { // ITU-T Rec. H.222.1 type E stream if (pes_data.stream_type === StreamType.kPESPrivateData) { var payload_start_index = 6; var payload_length = void 0; if (PES_packet_length !== 0) { payload_length = PES_packet_length; } else { // PES_packet_length === 0 payload_length = data.byteLength - payload_start_index; } var payload = data.subarray(payload_start_index, payload_start_index + payload_length); this.parsePESPrivateDataPayload(payload, undefined, undefined, pes_data.pid, stream_id); } } }; TSDemuxer.prototype.parseH264Payload = function (data, pts, dts, file_position, random_access_indicator) { var annexb_parser = new H264AnnexBParser(data); var nalu_payload = null; var units = []; var length = 0; var keyframe = false; while ((nalu_payload = annexb_parser.readNextNaluPayload()) != null) { var nalu_avc1 = new H264NaluAVC1(nalu_payload); if (nalu_avc1.type === H264NaluType.kSliceSPS) { // Notice: parseSPS requires Nalu without startcode or length-header var sps_details = SPSParser.parseSPS(nalu_payload.data); if (!this.video_init_segment_dispatched_) { this.video_metadata_.sps = nalu_avc1; this.video_metadata_.sps_details = sps_details; } else if (this.detectVideoMetadataChange(nalu_avc1, sps_details) === true) { Log.v(this.TAG, "H264: Critical h264 metadata has been changed, attempt to re-generate InitSegment"); this.video_metadata_changed_ = true; this.video_metadata_ = { sps: nalu_avc1, pps: undefined, sps_details: sps_details }; } } else if (nalu_avc1.type === H264NaluType.kSlicePPS) { if (!this.video_init_segment_dispatched_ || this.video_metadata_changed_) { this.video_metadata_.pps = nalu_avc1; if (this.video_metadata_.sps && this.video_metadata_.pps) { if (this.video_metadata_changed_) { // flush stashed frames before changing codec metadata this.dispatchVideoMediaSegment(); } // notify new codec metadata (maybe changed) this.dispatchVideoInitSegment(); } } } else if (nalu_avc1.type === H264NaluType.kSliceIDR) { keyframe = true; } else if (nalu_avc1.type === H264NaluType.kSliceNonIDR && random_access_indicator === 1) { // For open-gop stream, use random_access_indicator to identify keyframe keyframe = true; } // Push samples to remuxer only if initialization metadata has been dispatched if (this.video_init_segment_dispatched_) { units.push(nalu_avc1); length += nalu_avc1.data.byteLength; } } var pts_ms = Math.floor(pts / this.timescale_); var dts_ms = Math.floor(dts / this.timescale_); if (units.length) { var track = this.video_track_; var avc_sample = { units: units, length: length, isKeyframe: keyframe, dts: dts_ms, pts: pts_ms, cts: pts_ms - dts_ms, file_position: file_position }; track.samples.push(avc_sample); track.length += length; } }; TSDemuxer.prototype.detectVideoMetadataChange = function (new_sps, new_sps_details) { if (new_sps.data.byteLength !== this.video_metadata_.sps.data.byteLength) { return true; } if (new_sps_details.codec_mimetype !== this.video_metadata_.sps_details.codec_mimetype) { Log.v(this.TAG, "H264: Codec mimeType changed from " + "".concat(this.video_metadata_.sps_details.codec_mimetype, " to ").concat(new_sps_details.codec_mimetype)); return true; } if (new_sps_details.codec_size.width !== this.video_metadata_.sps_details.codec_size.width || new_sps_details.codec_size.height !== this.video_metadata_.sps_details.codec_size.height) { var old_size = this.video_metadata_.sps_details.codec_size; var new_size = new_sps_details.codec_size; Log.v(this.TAG, "H264: Coded Resolution changed from " + "".concat(old_size.width, "x").concat(old_size.height, " to ").concat(new_size.width, "x").concat(new_size.height)); return true; } if (new_sps_details.present_size.width !== this.video_metadata_.sps_details.present_size.width) { Log.v(this.TAG, "H264: Present resolution width changed from " + "".concat(this.video_metadata_.sps_details.present_size.width, " to ").concat(new_sps_details.present_size.width)); return true; } return false; }; TSDemuxer.prototype.isInitSegmentDispatched = function () { if (this.has_video_ && this.has_audio_) { // both video & audio return this.video_init_segment_dispatched_ && this.audio_init_segment_dispatched_; } if (this.has_video_ && !this.has_audio_) { // video only return this.video_init_segment_dispatched_; } if (!this.has_video_ && this.has_audio_) { // audio only return this.audio_init_segment_dispatched_; } return false; }; TSDemuxer.prototype.dispatchVideoInitSegment = function () { var sps_details = this.video_metadata_.sps_details; var meta = {}; meta.type = 'video'; meta.id = this.video_track_.id; meta.timescale = 1000; meta.duration = this.duration_; meta.codecWidth = sps_details.codec_size.width; meta.codecHeight = sps_details.codec_size.height; meta.presentWidth = sps_details.present_size.width; meta.presentHeight = sps_details.present_size.height; meta.profile = sps_details.profile_string; meta.level = sps_details.level_string; meta.bitDepth = sps_details.bit_depth; meta.chromaFormat = sps_details.chroma_format; meta.sarRatio = sps_details.sar_ratio; meta.frameRate = sps_details.frame_rate; var fps_den = meta.frameRate.fps_den; var fps_num = meta.frameRate.fps_num; meta.refSampleDuration = 1000 * (fps_den / fps_num); meta.codec = sps_details.codec_mimetype; var sps_without_header = this.video_metadata_.sps.data.subarray(4); var pps_without_header = this.video_metadata_.pps.data.subarray(4); var avcc = new AVCDecoderConfigurationRecord(sps_without_header, pps_without_header, sps_details); meta.avcc = avcc.getData(); if (this.video_init_segment_dispatched_ == false) { Log.v(this.TAG, "Generated first AVCDecoderConfigurationRecord for mimeType: ".concat(meta.codec)); } this.onTrackMetadata('video', meta); this.video_init_segment_dispatched_ = true; this.video_metadata_changed_ = false; // notify new MediaInfo var mi = this.media_info_; mi.hasVideo = true; mi.width = meta.codecWidth; mi.height = meta.codecHeight; mi.fps = meta.frameRate.fps; mi.profile = meta.profile; mi.level = meta.level; mi.refFrames = sps_details.ref_frames; mi.chromaFormat = sps_details.chroma_format_string; mi.sarNum = meta.sarRatio.width; mi.sarDen = meta.sarRatio.height; mi.videoCodec = meta.codec; if (mi.hasAudio && mi.audioCodec) { mi.mimeType = "video/mp2t; codecs=\"".concat(mi.videoCodec, ",").concat(mi.audioCodec, "\""); } else { mi.mimeType = "video/mp2t; codecs=\"".concat(mi.videoCodec, "\""); } if (mi.isComplete()) { this.onMediaInfo(mi); } }; TSDemuxer.prototype.dispatchVideoMediaSegment = function () { if (this.isInitSegmentDispatched()) { if (this.video_track_.length) { this.onDataAvailable(null, this.video_track_); } } }; TSDemuxer.prototype.dispatchAudioMediaSegment = function () { if (this.isInitSegmentDispatched()) { if (this.audio_track_.length) { this.onDataAvailable(this.audio_track_, null); } } }; TSDemuxer.prototype.dispatchAudioVideoMediaSegment = function () { if (this.isInitSegmentDispatched()) { if (this.audio_track_.length || this.video_track_.length) { this.onDataAvailable(this.audio_track_, this.video_track_); } } }; TSDemuxer.prototype.parseAACPayload = function (data, pts) { if (this.has_video_ && !this.video_init_segment_dispatched_) { // If first video IDR frame hasn't been detected, // Wait for first IDR frame and video init segment being dispatched return; } if (this.aac_last_incomplete_data_) { var buf = new Uint8Array(data.byteLength + this.aac_last_incomplete_data_.byteLength); buf.set(this.aac_last_incomplete_data_, 0); buf.set(data, this.aac_last_incomplete_data_.byteLength); data = buf; } var ref_sample_duration; var base_pts_ms; if (pts != undefined) { base_pts_ms = pts / this.timescale_; } else if (this.aac_last_sample_pts_ != undefined) { ref_sample_duration = 1024 / this.audio_metadata_.sampling_frequency * 1000; base_pts_ms = this.aac_last_sample_pts_ + ref_sample_duration; } else { Log.w(this.TAG, "AAC: Unknown pts"); return; } if (this.aac_last_incomplete_data_ && this.aac_last_sample_pts_) { ref_sample_duration = 1024 / this.audio_metadata_.sampling_frequency * 1000; var new_pts_ms = this.aac_last_sample_pts_ + ref_sample_duration; if (Math.abs(new_pts_ms - base_pts_ms) > 1) { Log.w(this.TAG, "AAC: Detected pts overlapped, " + "expected: ".concat(new_pts_ms, "ms, PES pts: ").concat(base_pts_ms, "ms")); base_pts_ms = new_pts_ms; } } var adts_parser = new AACADTSParser(data); var aac_frame = null; var sample_pts_ms = base_pts_ms; var last_sample_pts_ms; while ((aac_frame = adts_parser.readNextAACFrame()) != null) { ref_sample_duration = 1024 / aac_frame.sampling_frequency * 1000; if (this.audio_init_segment_dispatched_ == false) { this.audio_metadata_.audio_object_type = aac_frame.audio_object_type; this.audio_metadata_.sampling_freq_index = aac_frame.sampling_freq_index; this.audio_metadata_.sampling_frequency = aac_frame.sampling_frequency; this.audio_metadata_.channel_config = aac_frame.channel_config; this.dispatchAudioInitSegment(aac_frame); } else if (this.detectAudioMetadataChange(aac_frame)) { // flush stashed frames before notify new AudioSpecificConfig this.dispatchAudioMediaSegment(); // notify new AAC AudioSpecificConfig this.dispatchAudioInitSegment(aac_frame); } last_sample_pts_ms = sample_pts_ms; var sample_pts_ms_int = Math.floor(sample_pts_ms); var aac_sample = { unit: aac_frame.data, length: aac_frame.data.byteLength, pts: sample_pts_ms_int, dts: sample_pts_ms_int }; this.audio_track_.samples.push(aac_sample); this.audio_track_.length += aac_frame.data.byteLength; sample_pts_ms += ref_sample_duration; } if (adts_parser.hasIncompleteData()) { this.aac_last_incomplete_data_ = adts_parser.getIncompleteData(); } if (last_sample_pts_ms) { this.aac_last_sample_pts_ = last_sample_pts_ms; } }; TSDemuxer.prototype.detectAudioMetadataChange = function (frame) { if (frame.audio_object_type !== this.audio_metadata_.audio_object_type) { Log.v(this.TAG, "AAC: AudioObjectType changed from " + "".concat(this.audio_metadata_.audio_object_type, " to ").concat(frame.audio_object_type)); return true; } if (frame.sampling_freq_index !== this.audio_metadata_.sampling_freq_index) { Log.v(this.TAG, "AAC: SamplingFrequencyIndex changed from " + "".concat(this.audio_metadata_.sampling_freq_index, " to ").concat(frame.sampling_freq_index)); return true; } if (frame.channel_config !== this.audio_metadata_.channel_config) { Log.v(this.TAG, "AAC: Channel configuration changed from " + "".concat(this.audio_metadata_.channel_config, " to ").concat(frame.channel_config)); return true; } return false; }; TSDemuxer.prototype.dispatchAudioInitSegment = function (aac_frame) { var audio_specific_config = new AudioSpecificConfig(aac_frame); var meta = {}; meta.type = 'audio'; meta.id = this.audio_track_.id; meta.timescale = 1000; meta.duration = this.duration_; meta.audioSampleRate = audio_specific_config.sampling_rate; meta.channelCount = audio_specific_config.channel_count; meta.codec = audio_specific_config.codec_mimetype; meta.originalCodec = audio_specific_config.original_codec_mimetype; meta.config = audio_specific_config.config; meta.refSampleDuration = 1024 / meta.audioSampleRate * meta.timescale; if (this.audio_init_segment_dispatched_ == false) { Log.v(this.TAG, "Generated first AudioSpecificConfig for mimeType: ".concat(meta.codec)); } this.onTrackMetadata('audio', meta); this.audio_init_segment_dispatched_ = true; this.video_metadata_changed_ = false; // notify new MediaInfo var mi = this.media_info_; mi.hasAudio = true; mi.audioCodec = meta.originalCodec; mi.audioSampleRate = meta.audioSampleRate; mi.audioChannelCount = meta.channelCount; if (mi.hasVideo && mi.videoCodec) { mi.mimeType = "video/mp2t; codecs=\"".concat(mi.videoCodec, ",").concat(mi.audioCodec, "\""); } else { mi.mimeType = "video/mp2t; codecs=\"".concat(mi.audioCodec, "\""); } if (mi.isComplete()) { this.onMediaInfo(mi); } }; TSDemuxer.prototype.dispatchPESPrivateDataDescriptor = function (pid, stream_type, descriptor) { var desc = new PESPrivateDataDescriptor(); desc.pid = pid; desc.stream_type = stream_type; desc.descriptor = descriptor; if (this.onPESPrivateDataDescriptor) { this.onPESPrivateDataDescriptor(desc); } }; TSDemuxer.prototype.parsePESPrivateDataPayload = function (data, pts, dts, pid, stream_id) { var private_data = new PESPrivateData(); private_data.pid = pid; private_data.stream_id = stream_id; private_data.len = data.byteLength; private_data.data = data; if (pts != undefined) { var pts_ms = Math.floor(pts / this.timescale_); private_data.pts = pts_ms; } else { private_data.nearest_pts = this.aac_last_sample_pts_; } if (dts != undefined) { var dts_ms = Math.floor(dts / this.timescale_); private_data.dts = dts_ms; } if (this.onPESPrivateData) { this.onPESPrivateData(private_data); } }; return TSDemuxer; }(BaseDemuxer)); export default TSDemuxer; //# sourceMappingURL=ts-demuxer.js.map