Buckets:
ktongue/docker_container / simsite /frontend /node_modules /hls.js /src /utils /mediacapabilities-helper.ts
| import { | |
| fillInMissingAV01Params, | |
| getCodecsForMimeType, | |
| mimeTypeForCodec, | |
| userAgentHevcSupportIsInaccurate, | |
| } from './codecs'; | |
| import { isHEVC } from './mp4-tools'; | |
| import type { AudioTracksByGroup } from './rendition-helper'; | |
| import type { Level, VideoRange } from '../types/level'; | |
| import type { AudioSelectionOption } from '../types/media-playlist'; | |
| export type MediaDecodingInfo = { | |
| supported: boolean; | |
| configurations: readonly MediaDecodingConfiguration[]; | |
| decodingInfoResults: readonly MediaCapabilitiesDecodingInfo[]; | |
| error?: Error; | |
| }; | |
| // @ts-ignore | |
| const supportedResult: MediaCapabilitiesDecodingInfo = { | |
| supported: true, | |
| powerEfficient: true, | |
| smooth: true, | |
| // keySystemAccess: null, | |
| }; | |
| // @ts-ignore | |
| const unsupportedResult: MediaCapabilitiesDecodingInfo = { | |
| supported: false, | |
| smooth: false, | |
| powerEfficient: false, | |
| // keySystemAccess: null, | |
| }; | |
| export const SUPPORTED_INFO_DEFAULT: MediaDecodingInfo = { | |
| supported: true, | |
| configurations: [] as MediaDecodingConfiguration[], | |
| decodingInfoResults: [supportedResult], | |
| } as const; | |
| export function getUnsupportedResult( | |
| error: Error, | |
| configurations: MediaDecodingConfiguration[], | |
| ): MediaDecodingInfo { | |
| return { | |
| supported: false, | |
| configurations, | |
| decodingInfoResults: [unsupportedResult], | |
| error, | |
| }; | |
| } | |
| export function requiresMediaCapabilitiesDecodingInfo( | |
| level: Level, | |
| audioTracksByGroup: AudioTracksByGroup, | |
| currentVideoRange: VideoRange | undefined, | |
| currentFrameRate: number, | |
| currentBw: number, | |
| audioPreference: AudioSelectionOption | undefined, | |
| ): boolean { | |
| // Only test support when configuration is exceeds minimum options | |
| const videoCodecs = level.videoCodec; | |
| const audioGroups = level.audioCodec ? level.audioGroups : null; | |
| const audioCodecPreference = audioPreference?.audioCodec; | |
| const channelsPreference = audioPreference?.channels; | |
| const maxChannels = channelsPreference | |
| ? parseInt(channelsPreference) | |
| : audioCodecPreference | |
| ? Infinity | |
| : 2; | |
| let audioChannels: Record<string, number> | null = null; | |
| if (audioGroups?.length) { | |
| try { | |
| if (audioGroups.length === 1 && audioGroups[0]) { | |
| audioChannels = audioTracksByGroup.groups[audioGroups[0]].channels; | |
| } else { | |
| audioChannels = audioGroups.reduce( | |
| (acc, groupId) => { | |
| if (groupId) { | |
| const audioTrackGroup = audioTracksByGroup.groups[groupId]; | |
| if (!audioTrackGroup) { | |
| throw new Error(`Audio track group ${groupId} not found`); | |
| } | |
| // Sum all channel key values | |
| Object.keys(audioTrackGroup.channels).forEach((key) => { | |
| acc[key] = (acc[key] || 0) + audioTrackGroup.channels[key]; | |
| }); | |
| } | |
| return acc; | |
| }, | |
| { 2: 0 }, | |
| ); | |
| } | |
| } catch (error) { | |
| return true; | |
| } | |
| } | |
| return ( | |
| (videoCodecs !== undefined && | |
| // Force media capabilities check for HEVC to avoid failure on Windows | |
| (videoCodecs.split(',').some((videoCodec) => isHEVC(videoCodec)) || | |
| (level.width > 1920 && level.height > 1088) || | |
| (level.height > 1920 && level.width > 1088) || | |
| level.frameRate > Math.max(currentFrameRate, 30) || | |
| (level.videoRange !== 'SDR' && | |
| level.videoRange !== currentVideoRange) || | |
| level.bitrate > Math.max(currentBw, 8e6))) || | |
| (!!audioChannels && | |
| Number.isFinite(maxChannels) && | |
| Object.keys(audioChannels).some( | |
| (channels) => parseInt(channels) > maxChannels, | |
| )) | |
| ); | |
| } | |
| export function getMediaDecodingInfoPromise( | |
| level: Level, | |
| audioTracksByGroup: AudioTracksByGroup, | |
| mediaCapabilities: MediaCapabilities | undefined, | |
| cache: Record< | |
| string, | |
| Promise<MediaCapabilitiesDecodingInfo> | undefined | |
| > = {}, | |
| ): Promise<MediaDecodingInfo> { | |
| const videoCodecs = level.videoCodec; | |
| if ((!videoCodecs && !level.audioCodec) || !mediaCapabilities) { | |
| return Promise.resolve(SUPPORTED_INFO_DEFAULT); | |
| } | |
| const configurations: MediaDecodingConfiguration[] = []; | |
| const videoDecodeList = makeVideoConfigurations(level); | |
| const videoCount = videoDecodeList.length; | |
| const audioDecodeList = makeAudioConfigurations( | |
| level, | |
| audioTracksByGroup, | |
| videoCount > 0, | |
| ); | |
| const audioCount = audioDecodeList.length; | |
| for (let i = videoCount || 1 * audioCount || 1; i--; ) { | |
| const configuration: MediaDecodingConfiguration = { | |
| type: 'media-source', | |
| }; | |
| if (videoCount) { | |
| configuration.video = videoDecodeList[i % videoCount]; | |
| } | |
| if (audioCount) { | |
| configuration.audio = audioDecodeList[i % audioCount]; | |
| const audioBitrate = configuration.audio.bitrate; | |
| if (configuration.video && audioBitrate) { | |
| configuration.video.bitrate -= audioBitrate; | |
| } | |
| } | |
| configurations.push(configuration); | |
| } | |
| if (videoCodecs) { | |
| // Override Windows Firefox HEVC MediaCapabilities result (https://github.com/video-dev/hls.js/issues/7046) | |
| const ua = navigator.userAgent; | |
| if ( | |
| videoCodecs.split(',').some((videoCodec) => isHEVC(videoCodec)) && | |
| userAgentHevcSupportIsInaccurate() | |
| ) { | |
| return Promise.resolve( | |
| getUnsupportedResult( | |
| new Error( | |
| `Overriding Windows Firefox HEVC MediaCapabilities result based on user-agent string: (${ua})`, | |
| ), | |
| configurations, | |
| ), | |
| ); | |
| } | |
| } | |
| return Promise.all( | |
| configurations.map((configuration) => { | |
| // Cache MediaCapabilities promises | |
| const decodingInfoKey = getMediaDecodingInfoKey(configuration); | |
| return ( | |
| cache[decodingInfoKey] || | |
| (cache[decodingInfoKey] = mediaCapabilities.decodingInfo(configuration)) | |
| ); | |
| }), | |
| ) | |
| .then((decodingInfoResults) => ({ | |
| supported: !decodingInfoResults.some((info) => !info.supported), | |
| configurations, | |
| decodingInfoResults, | |
| })) | |
| .catch((error) => ({ | |
| supported: false, | |
| configurations, | |
| decodingInfoResults: [] as MediaCapabilitiesDecodingInfo[], | |
| error, | |
| })); | |
| } | |
| function makeVideoConfigurations(level: Level): VideoConfiguration[] { | |
| const videoCodecs = level.videoCodec?.split(','); | |
| const bitrate = getVariantDecodingBitrate(level); | |
| const width = level.width || 640; | |
| const height = level.height || 480; | |
| // Assume a framerate of 30fps since MediaCapabilities will not accept Level default of 0. | |
| const framerate = level.frameRate || 30; | |
| const videoRange = level.videoRange.toLowerCase() as 'sdr' | 'pq' | 'hlg'; | |
| return videoCodecs | |
| ? videoCodecs.map((videoCodec: string) => { | |
| const videoConfiguration: VideoConfiguration = { | |
| contentType: mimeTypeForCodec( | |
| fillInMissingAV01Params(videoCodec), | |
| 'video', | |
| ), | |
| width, | |
| height, | |
| bitrate, | |
| framerate, | |
| }; | |
| if (videoRange !== 'sdr') { | |
| videoConfiguration.transferFunction = videoRange as TransferFunction; | |
| } | |
| return videoConfiguration; | |
| }) | |
| : []; | |
| } | |
| function makeAudioConfigurations( | |
| level: Level, | |
| audioTracksByGroup: AudioTracksByGroup, | |
| hasVideo: boolean, | |
| ): AudioConfiguration[] { | |
| const audioCodecs = level.audioCodec?.split(','); | |
| const combinedBitrate = getVariantDecodingBitrate(level); | |
| if (audioCodecs && level.audioGroups) { | |
| return level.audioGroups.reduce((configurations, audioGroupId) => { | |
| const tracks = audioGroupId | |
| ? audioTracksByGroup.groups[audioGroupId]?.tracks | |
| : null; | |
| if (tracks) { | |
| return tracks.reduce((configs, audioTrack) => { | |
| if (audioTrack.groupId === audioGroupId) { | |
| const channelsNumber = parseFloat(audioTrack.channels || ''); | |
| audioCodecs.forEach((audioCodec) => { | |
| const audioConfiguration: AudioConfiguration = { | |
| contentType: mimeTypeForCodec(audioCodec, 'audio'), | |
| bitrate: hasVideo | |
| ? estimatedAudioBitrate(audioCodec, combinedBitrate) | |
| : combinedBitrate, | |
| }; | |
| if (channelsNumber) { | |
| audioConfiguration.channels = '' + channelsNumber; | |
| } | |
| configs.push(audioConfiguration); | |
| }); | |
| } | |
| return configs; | |
| }, configurations); | |
| } | |
| return configurations; | |
| }, [] as AudioConfiguration[]); | |
| } | |
| return []; | |
| } | |
| function estimatedAudioBitrate( | |
| audioCodec: string, | |
| levelBitrate: number, | |
| ): number { | |
| if (levelBitrate <= 1) { | |
| return 1; | |
| } | |
| let audioBitrate = 128000; | |
| if (audioCodec === 'ec-3') { | |
| audioBitrate = 768000; | |
| } else if (audioCodec === 'ac-3') { | |
| audioBitrate = 640000; | |
| } | |
| return Math.min(levelBitrate / 2, audioBitrate); // Don't exceed some % of level bitrate | |
| } | |
| function getVariantDecodingBitrate(level: Level): number { | |
| return ( | |
| Math.ceil(Math.max(level.bitrate * 0.9, level.averageBitrate) / 1000) * | |
| 1000 || 1 | |
| ); | |
| } | |
| function getMediaDecodingInfoKey(config: MediaDecodingConfiguration): string { | |
| let key = ''; | |
| const { audio, video } = config; | |
| if (video) { | |
| const codec = getCodecsForMimeType(video.contentType); | |
| key += `${codec}_r${video.height}x${video.width}f${Math.ceil(video.framerate)}${ | |
| video.transferFunction || 'sd' | |
| }_${Math.ceil(video.bitrate / 1e5)}`; | |
| } | |
| if (audio) { | |
| const codec = getCodecsForMimeType(audio.contentType); | |
| key += `${video ? '_' : ''}${codec}_c${audio.channels}`; | |
| } | |
| return key; | |
| } | |
Xet Storage Details
- Size:
- 9.54 kB
- Xet hash:
- de1f2730593e65d81a4e95b7ac28f0e9d2547b283cc25b0cf30c8c97db700f72
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.