Skip to content

Commit 26cc04d

Browse files
authored
Merge pull request #129 from anam-org/feat/sli-metrics-and-stats
feat: add fallback video frame detection methods for safari
2 parents 79c1781 + 92529c9 commit 26cc04d

File tree

2 files changed

+57
-26
lines changed

2 files changed

+57
-26
lines changed

src/lib/ClientMetrics.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,11 @@ export const createRTCStatsReport = (
148148

149149
inboundVideo.forEach((report) => {
150150
const videoData = {
151-
framesReceived: report.framesReceived || 'unknown',
152-
framesDropped: report.framesDropped || 'unknown',
153-
framesPerSecond: report.framesPerSecond || 'unknown',
154-
packetsReceived: report.packetsReceived || 'unknown',
155-
packetsLost: report.packetsLost || 'unknown',
151+
framesReceived: report.framesReceived ?? 'unknown',
152+
framesDropped: report.framesDropped ?? 'unknown',
153+
framesPerSecond: report.framesPerSecond ?? 'unknown',
154+
packetsReceived: report.packetsReceived ?? 'unknown',
155+
packetsLost: report.packetsLost ?? 'unknown',
156156
resolution:
157157
report.frameWidth && report.frameHeight
158158
? `${report.frameWidth}x${report.frameHeight}`
@@ -172,9 +172,9 @@ export const createRTCStatsReport = (
172172

173173
inboundAudio.forEach((report) => {
174174
const audioData = {
175-
packetsReceived: report.packetsReceived || 'unknown',
176-
packetsLost: report.packetsLost || 'unknown',
177-
audioLevel: report.audioLevel || 'unknown',
175+
packetsReceived: report.packetsReceived ?? 'unknown',
176+
packetsLost: report.packetsLost ?? 'unknown',
177+
audioLevel: report.audioLevel ?? 'unknown',
178178
jitter: report.jitter !== undefined ? report.jitter : undefined,
179179
totalAudioEnergy:
180180
report.totalAudioEnergy !== undefined
@@ -194,8 +194,8 @@ export const createRTCStatsReport = (
194194

195195
outboundAudio.forEach((report) => {
196196
const userAudioData = {
197-
packetsSent: report.packetsSent || 'unknown',
198-
retransmittedPackets: report.retransmittedPacketsSent || undefined,
197+
packetsSent: report.packetsSent ?? 'unknown',
198+
retransmittedPackets: report.retransmittedPacketsSent ?? undefined,
199199
avgPacketSendDelay:
200200
report.totalPacketSendDelay !== undefined
201201
? (report.totalPacketSendDelay / (report.packetsSent || 1)) * 1000

src/modules/StreamingClient.ts

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -146,23 +146,50 @@ export class StreamingClient {
146146

147147
try {
148148
const stats = await this.peerConnection.getStats();
149+
150+
let videoDetected = false;
151+
let detectionMethod = null;
152+
149153
stats.forEach((report) => {
150154
// Find the report for inbound video
151155
if (report.type === 'inbound-rtp' && report.kind === 'video') {
152-
if (report.framesReceived > 0) {
153-
this.successMetricFired = true;
154-
sendClientMetric(
155-
ClientMetricMeasurement.CLIENT_METRIC_MEASUREMENT_SESSION_SUCCESS,
156-
'1',
157-
);
158-
if (this.successMetricPoller) {
159-
clearInterval(this.successMetricPoller);
160-
}
161-
clearTimeout(timeoutId);
162-
this.successMetricPoller = null;
156+
// Method 1: Try framesDecoded (most reliable when available)
157+
if (
158+
report.framesDecoded !== undefined &&
159+
report.framesDecoded > 0
160+
) {
161+
videoDetected = true;
162+
detectionMethod = 'framesDecoded';
163+
} else if (
164+
report.framesReceived !== undefined &&
165+
report.framesReceived > 0
166+
) {
167+
videoDetected = true;
168+
detectionMethod = 'framesReceived';
169+
} else if (
170+
report.bytesReceived > 0 &&
171+
report.packetsReceived > 0 &&
172+
// Additional check: ensure we've received enough data for actual video
173+
report.bytesReceived > 100000 // rough threshold
174+
) {
175+
videoDetected = true;
176+
detectionMethod = 'bytesReceived';
163177
}
164178
}
165179
});
180+
if (videoDetected && !this.successMetricFired) {
181+
this.successMetricFired = true;
182+
sendClientMetric(
183+
ClientMetricMeasurement.CLIENT_METRIC_MEASUREMENT_SESSION_SUCCESS,
184+
'1',
185+
detectionMethod ? { detectionMethod } : undefined,
186+
);
187+
if (this.successMetricPoller) {
188+
clearInterval(this.successMetricPoller);
189+
}
190+
clearTimeout(timeoutId);
191+
this.successMetricPoller = null;
192+
}
166193
} catch (error) {}
167194
}, 500);
168195
}
@@ -448,6 +475,14 @@ export class StreamingClient {
448475
// unregister the callback after the first frame
449476
this.videoElement?.cancelVideoFrameCallback(handle);
450477
this.publicEventEmitter.emit(AnamEvent.VIDEO_PLAY_STARTED);
478+
if (!this.successMetricFired) {
479+
this.successMetricFired = true;
480+
sendClientMetric(
481+
ClientMetricMeasurement.CLIENT_METRIC_MEASUREMENT_SESSION_SUCCESS,
482+
'1',
483+
{ detectionMethod: 'videoElement' },
484+
);
485+
}
451486
});
452487
}
453488
} else if (event.track.kind === 'audio') {
@@ -568,11 +603,7 @@ export class StreamingClient {
568603
private async shutdown() {
569604
if (this.showPeerConnectionStatsReport) {
570605
const stats = await this.peerConnection?.getStats();
571-
if (!stats) {
572-
console.error(
573-
'StreamingClient - shutdown: peer connection is unavailable. Unable to create RTC stats report.',
574-
);
575-
} else {
606+
if (stats) {
576607
const report = createRTCStatsReport(
577608
stats,
578609
this.peerConnectionStatsReportOutputFormat,

0 commit comments

Comments
 (0)