|
8 | 8 | } from '../../packetTrailer/packetTrailer'; |
9 | 9 | import type { PacketTrailerPublishOptions } from '../../packetTrailer/types'; |
10 | 10 | import { hasPacketTrailerPublishOptions } from '../../packetTrailer/utils'; |
11 | | -import type { VideoCodec } from '../../room/track/options'; |
| 11 | +import { type VideoCodec, videoCodecs } from '../../room/track/options'; |
| 12 | +import { mimeTypeToVideoCodecString } from '../../room/track/utils'; |
12 | 13 | import { ENCRYPTION_ALGORITHM, IV_LENGTH, UNENCRYPTED_BYTES } from '../constants'; |
13 | 14 | import { CryptorError, CryptorErrorReason } from '../errors'; |
14 | 15 | import { type CryptorCallbacks, CryptorEvent } from '../events'; |
@@ -106,6 +107,12 @@ export class FrameCryptor extends BaseFrameCryptor { |
106 | 107 |
|
107 | 108 | private readonly ERROR_WINDOW_MS = 60000; // 1 minute window |
108 | 109 |
|
| 110 | + /** |
| 111 | + * Tracks (participant, trackId, payloadType) tuples for which we've already logged a NALU |
| 112 | + * fallback, so a persistent bad state doesn't flood the console (Firefox doesn't filter debug). |
| 113 | + */ |
| 114 | + private loggedNALUFallbacks: Set<string> = new Set(); |
| 115 | + |
109 | 116 | constructor(opts: { |
110 | 117 | keys: ParticipantKeyHandler; |
111 | 118 | participantIdentity: string; |
@@ -795,36 +802,64 @@ export class FrameCryptor extends BaseFrameCryptor { |
795 | 802 | } |
796 | 803 |
|
797 | 804 | // Try NALU processing for H.264/H.265 codecs |
| 805 | + const payloadType = frame.getMetadata().payloadType; |
| 806 | + const fallbackKey = `${this.participantIdentity}-${this.trackId}-${payloadType}`; |
798 | 807 | try { |
799 | 808 | const knownCodec = |
800 | 809 | detectedCodec === 'h264' || detectedCodec === 'h265' ? detectedCodec : undefined; |
801 | 810 | const naluResult = processNALUsForEncryption(new Uint8Array(frame.data), knownCodec); |
802 | 811 |
|
803 | 812 | if (naluResult.requiresNALUProcessing) { |
| 813 | + // Recovered for this tuple, allow a future failure to log again. |
| 814 | + this.loggedNALUFallbacks.delete(fallbackKey); |
804 | 815 | return { |
805 | 816 | unencryptedBytes: naluResult.unencryptedBytes, |
806 | 817 | requiresNALUProcessing: true, |
807 | 818 | }; |
808 | 819 | } |
809 | 820 | } catch (e) { |
810 | | - workerLogger.debug('NALU processing failed, falling back to VP8 handling', { |
811 | | - error: e, |
812 | | - ...this.logContext, |
813 | | - }); |
| 821 | + this.logNALUFallbackOnce(fallbackKey, payloadType, e); |
814 | 822 | } |
815 | 823 |
|
816 | 824 | // Fallback to VP8 handling |
817 | 825 | return { unencryptedBytes: UNENCRYPTED_BYTES[frame.type], requiresNALUProcessing: false }; |
818 | 826 | } |
819 | 827 |
|
820 | 828 | /** |
821 | | - * inspects frame payloadtype if available and maps it to the codec specified in rtpMap |
| 829 | + * Logs a NALU processing fallback at most once per (participant, trackId, payloadType) tuple, |
| 830 | + * so a persistent bad state doesn't flood the console (Firefox doesn't filter debug). |
| 831 | + */ |
| 832 | + private logNALUFallbackOnce( |
| 833 | + fallbackKey: string, |
| 834 | + payloadType: number | undefined, |
| 835 | + error: unknown, |
| 836 | + ) { |
| 837 | + if (this.loggedNALUFallbacks.has(fallbackKey)) { |
| 838 | + return; |
| 839 | + } |
| 840 | + this.loggedNALUFallbacks.add(fallbackKey); |
| 841 | + workerLogger.warn('NALU processing failed, falling back to VP8 handling', { |
| 842 | + error, |
| 843 | + payloadType, |
| 844 | + ...this.logContext, |
| 845 | + }); |
| 846 | + } |
| 847 | + |
| 848 | + /** |
| 849 | + * inspects frame mimetype if available. falls back to payloadtype and maps it to the codec specified in rtpMap |
822 | 850 | */ |
823 | 851 | private getVideoCodec(frame: RTCEncodedVideoFrame): VideoCodec | undefined { |
| 852 | + const metadata = frame.getMetadata(); |
| 853 | + if (metadata.mimeType) { |
| 854 | + const maybeKnownCodec = mimeTypeToVideoCodecString(metadata.mimeType); |
| 855 | + if (videoCodecs.includes(maybeKnownCodec)) { |
| 856 | + return maybeKnownCodec; |
| 857 | + } |
| 858 | + } |
824 | 859 | if (this.rtpMap.size === 0) { |
825 | 860 | return undefined; |
826 | 861 | } |
827 | | - const payloadType = frame.getMetadata().payloadType; |
| 862 | + const payloadType = metadata.payloadType; |
828 | 863 | const codec = payloadType ? this.rtpMap.get(payloadType) : undefined; |
829 | 864 | return codec; |
830 | 865 | } |
|
0 commit comments