Skip to content

Video Quality Test (iOS)

VideoQualityTest measures playback from YouTube and public Netflix Tudum trailers in a WKWebView. It collects playback events, computes the video QoE scores on the device, and uploads the finished report through the normal measurement queue.

Build the test only after registration has completed. The registration key must allow the VIDEO type and the service's subject.

The test is available on iOS 15 and later. It is not available on tvOS.

Starting a test

Give the builder a service and a supported URL. A container view is optional, but lets the app show the video while it is measured. Keep a strong reference to the test until onTestResult or onTestError runs.

import SurfmeterQualitySDK
import UIKit

final class VideoRunner: QualityTestResultListener {
    private var test: VideoQualityTest?

    func start(in container: UIView) {
        do {
            let builder = VideoQualityTestBuilder(
                service: .youtube,
                url: URL(string: "https://www.youtube.com/embed/aqz-KE-bpKQ")!
            )
            .setMaxPlaybackDuration(durationMs: 30_000)
            .setMaxTestDuration(durationMs: 60_000)
            .setContainerView(view: container)
            .setResultListener(listener: self)

            test = try builder.build()
            try test?.start()
        } catch {
            print("Could not start test: \(error)")
            test = nil
        }
    }

    func onTestResult(_ result: [String: AnyHashable]) {
        print(result)
        test = nil
    }

    func onTestError(_ error: String) {
        print("Test failed: \(error)")
        test = nil
    }

    func onTestStateChanged(_ state: QualityTestState) {}
    func onPlaybackProgress(_ progress: Int) {}
}

start() and stop() must run on the main thread. stop() ends playback and starts report generation. It does not deliver the result immediately, so keep the test reference until a final result or error callback arrives. Do not release it when the state changes to STATE_END.

The defaults measure 10 seconds of media playback and allow 30 seconds for the whole test. The maximum test duration includes page loading, consent, and playback, and must be at least the playback duration.

Supported services and URLs

Use VideoService.youtube for YouTube and VideoService.netflix for Netflix. VideoService.netflix uses the server subject netflix_trailer automatically.

For YouTube, the SDK accepts a regular watch URL, a youtu.be short URL, or an embed URL. It converts each accepted URL into an autoplaying inline embed URL before loading it. An embed URL is a good default, especially when testing high resolutions.

VideoQualityTestBuilder(
    service: .youtube,
    url: URL(string: "https://www.youtube.com/embed/aqz-KE-bpKQ")!
)

For Netflix, use a public trailer URL from the Netflix Tudum site. The URL must have the form https://www.netflix.com/tudum/videos/<trailer-slug> with no query string or fragment. Regular Netflix title and watch URLs require a login and are not supported.

VideoQualityTestBuilder(
    service: .netflix,
    url: URL(string: "https://www.netflix.com/tudum/videos/emily-in-paris-season-3-recap")!
)

Builder options

VideoQualityTestBuilder supports these options in addition to the service and URL:

  • setMaxPlaybackDuration(durationMs:) sets the measured media-playback duration.
  • setMaxTestDuration(durationMs:) sets the wall-clock deadline for loading and playback.
  • setContainerView(view:) displays the WebView inside a UIView. The WebView keeps its configured pixel surface and scales to fit the container.
  • setResultListener(listener:) receives the result, errors, state changes, and playback progress.
  • setMetadata(_:) adds your own values to the report.
  • setCalculationSettings(_:) overrides the default on-device QoE calculation settings.

Cookies and website data

The test clears WebKit cookies, cache, history, and website storage by default. This gives repeated measurements a clean starting state.

Use setClearBrowsingData(false) when playback needs an existing login or consent session:

builder.setClearBrowsingData(false)

The default WebKit data store is shared with your app's other WKWebView instances. Clearing it also removes their cookies and website data.

Muting playback

Playback starts muted by default, so an automated measurement does not unexpectedly play audio. To start with audio, use setMuted(false). You can also call test.setMuted(_:) while the test runs. This changes the page's media elements and does not change the device volume.

builder.setMuted(false)

Measuring higher resolutions

The default WebView surface is 1920x1080 device pixels. YouTube normally limits the selected rendition to what its playback surface can display. To request a 4K surface, set the WebView size and the display size used for QoE scoring to 3840x2160:

try builder.setWebViewDimensions(width: 3840, height: 2160)
try builder.setReportedDisplaySize(width: 3840, height: 2160)

The device must support the selected resolution and codec. A 4K surface uses substantially more WebKit and GPU memory.

Forcing a YouTube resolution

You can request one YouTube video height, or allow adaptation inside a range. This option applies to YouTube only.

builder.setForceResolution(height: 2160)
// or
builder.setForceResolution(min: 720, max: 1080)

Valid heights are 144, 240, 360, 480, 720, 1080, 1440, and 2160. A pinned resolution may stall when the network cannot sustain it. A range lets YouTube adapt between its bounds. The WebView surface and device decoder still limit the rendition that can play.

Results and lifecycle

QualityTestResultListener receives exactly one final callback per run: onTestResult(_:) for a completed measurement or onTestError(_:) when the test cannot finish. The successful report has the same shape as the other mobile quality tests. Its scores are under statistic_values; see Measurement Data for the available fields.

onPlaybackProgress(_:) reports playback progress from 0 to 100. onTestStateChanged(_:) reports STATE_READY, STATE_STARTING, STATE_STARTED, STATE_PLAYING, STATE_ANALYZING, and the final state. The app must remain in the foreground while the test runs.