A Kotlin Multiplatform debug SDK for Android and iOS apps that inspects HLS and DASH streaming in real time — on top of Media3/ExoPlayer on Android and AVFoundation/AVPlayer on iOS — surfacing track variants, segment metrics, CDN headers, ABR/track switches, DRM lifecycle, and playback errors through an in-app draggable overlay on both platforms.
streamprobe_demo_v2.mp4
⚠️ Development-only tool. StreamProbe is designed for debug builds and should be omitted from release builds by gating it behind a host-managed mechanism (BuildConfig.DEBUGon Android, a#if DEBUGcompilation condition on iOS). It is not an analytics, QoE, or production monitoring product.
Before you begin, ensure your project meets the following requirements:
Android
- Minimum SDK: Android 6.0 (API level 23) or higher.
- Media3/ExoPlayer: Version
1.10.0or higher. - Kotlin: Version
1.9.0+ (currently built against2.3.20).
iOS
- Minimum deployment target: iOS 15.0 or higher.
- Player: AVFoundation
AVPlayer. - Distribution: Swift Package Manager (Xcode 15+ / Swift 5.9+).
When something goes wrong with stream delivery — the wrong rendition gets picked, an unexpected ABR switch happens, segment latency spikes, or a CDN cache miss slips through — the developer's options today are all awkward, and the problem looks the same whether the app runs on Android/ExoPlayer or iOS/AVPlayer:
- A proxy (Charles, Proxyman): wire the device through a proxy, install an SSL cert, filter traffic by hand.
- Platform logs: sift through raw player logs —
adb logcat(ExoPlayer) on Android, Console.app /os_logandAVPlayerItemAccessLogdumps on iOS — looking for the relevant lines. - Manual manifest inspection: copy the URL into a browser and read the
.m3u8/ MPD by eye. - Usually, all three at once.
Each round of this takes 15–30 minutes, depends on a tethered device, requires external tools, and can't be handed off to a teammate. StreamProbe pulls the whole workflow into the app itself: the moment the SDK is attached, everything is captured automatically and readable through an on-screen overlay — on both platforms.
The platforms' own debug helpers don't close this gap. ExoPlayer's EventLogger / DebugTextViewHelper and iOS's AVPlayerItemAccessLog / AVPlayerItemErrorLog only surface player-level events and aggregate playback stats — they don't touch manifest contents, CDN headers, or per-segment timing. StreamProbe sits one layer below the player events and one above the raw transport to fill it.
StreamProbe is built around a single shared diagnostics core (Kotlin commonMain) that both platforms feed and read from. Each platform contributes only a thin observation adapter — which maps native player events into SDK models — and a native overlay renderer. Everything in between is shared verbatim.
Android (debug) iOS (debug)
┌────────────────────────────┐ ┌────────────────────────────┐
│ Media3 / ExoPlayer │ │ AVFoundation / AVPlayer │
│ AnalyticsListener │ │ AVPlayerProbe (Swift): │
│ (PlayerInterceptor): │ │ access/error-log + KVO + │
│ maps events → SDK models │ │ AVAssetVariant discovery │
└──────────────┬─────────────┘ └──────────────┬─────────────┘
│ diagnostics │ diagnostics
└──────────────────┬───────────────────┘ (via Core sink)
▼
┌──────────────────────────────────────────────────────────┐
│ StreamProbe shared core (commonMain) │
│ │
│ SessionStore — in-memory StateFlow session store │
│ OverlayPresenter → StateFlow<OverlayViewState> │
│ models · formatters · parsers · timing registries │
└────────────────────────────┬───────────────────────────────┘
│ view state (reads)
┌────────────────┴───────────────────┐
▼ ▼
┌────────────────────────┐ ┌────────────────────────┐
│ Debug Overlay — Android│ │ Debug Overlay — iOS │
│ Views (addContentView) │ │ UIKit (own UIWindow) │
└────────────────────────┘ └────────────────────────┘
│
▼
HLS / DASH origin + CDN
A single in-memory SessionStore (StateFlow-based) holds the captured session; OverlayPresenter projects it into a render-ready OverlayViewState. Both live in commonMain and are identical across platforms — only the adapter that fills the store and the renderer that draws the overlay differ. StreamProbe is distributed as a standard dependency; host apps guard attach() / show() behind a debug flag (BuildConfig.DEBUG on Android, #if DEBUG on iOS) for zero overhead in release builds.
A single Media3 AnalyticsListener (PlayerInterceptor) wired to the player feeds the shared store:
- Track snapshot — built from
player.currentTracksononTracksChanged; all video variants, audio renditions, and subtitle tracks are enumerated into a protocol-agnosticTracksSnapshot(works identically for HLS and DASH). Each track carries anisSelectedflag that drives the active-dot indicator in the overlay without secondary comparison. - Segment metrics and CDN headers — captured on
onLoadCompleted; per-segment download duration, size, throughput, and HTTP response headers (including cache hit/miss status) are stored for the session. - Track switch events —
VideoSwitchevents are emitted fromonVideoInputFormatChanged(the decoder-level format change is the authoritative signal); audio and subtitle switch events are emitted fromonDownstreamFormatChanged. Every video quality change, audio rendition switch, and subtitle selection (including disable) is recorded as aTrackSwitchEvent(VideoSwitch/AudioSwitch/SubtitleSwitch) with the previous/new track, buffer state at switch time, and the reason Media3 reports (INITIAL,ADAPTIVE,MANUAL,TRICKPLAY,UNKNOWN). Events are kept in a capped chronological list and displayed in the overlay's Switches tab.
On iOS the adapter is a native Swift AVPlayerProbe that observes AVFoundation — AVPlayerItem access/error-log notifications, KVO, and AVAssetVariant discovery — extracts primitive fields, and writes them into the shared store through the Core's pure mappers and a narrow write sink. On iOS 18+ it additionally consumes the AVMetricHLSMediaSegmentRequestEvent metrics stream (item.metrics(forType:)) to record true per-segment download metrics — duration, size, throughput, real CDN response headers, and measured network timing — reaching Android parity for the Segments tab; on iOS 17 and earlier it derives aggregate roll-ups from the access log instead. iOS ships as a two-layer package: a Kotlin StreamProbeCore binary (the shared core, compiled to a static XCFramework) plus the Swift StreamProbe layer that owns all AVFoundation I/O and renders the same overlay in UIKit. SKIE bridges the Kotlin StateFlow/sealed types to idiomatic Swift AsyncSequence/enums.
iOS data flow
AVPlayer ─ NSNotificationCenter / KVO / AVAssetVariant (Swift: AVPlayerProbe)
→ extract primitive fields (Swift)
→ StreamProbeCore pure mappers + write sink (Kotlin) → SessionStore
→ OverlayPresenter (Kotlin) → StateFlow<OverlayViewState>
─ (SKIE AsyncSequence) ─→ OverlayHostViewController (Swift / UIKit)
→ StreamProbeOverlayWindow (separate UIWindow at .alert+1)
Consumers import StreamProbe and get the Swift entry point plus (via @_exported) the shared Core types. The overlay ships inside the package, so iOS consumers get the full draggable panel out of the box — at parity with Android. Because AVFoundation exposes a coarser surface than Media3, a few features differ on iOS; see Known Limitations.
StreamProbe is distributed via Maven Central. Add the dependency to your app-level build.gradle.kts file:
dependencies {
// Add the core StreamProbe SDK
implementation("io.github.oguzhaneksi:streamprobe:<version_name>")
}Add StreamProbe via Xcode's File ▸ Add Package Dependencies… or in your Package.swift:
dependencies: [
.package(url: "https://github.com/oguzhaneksi/StreamProbe.git", from: "0.1.0")
],
targets: [
.target(
name: "YourApp",
dependencies: [.product(name: "StreamProbe", package: "StreamProbe")]
)
]Then import StreamProbe — you get the Swift entry point plus (via @_exported) the Core types. iOS is versioned independently of the Android (Maven Central) release.
Note: Even though it is an implementation dependency, we recommend gating the SDK activation behind debug checks in the usage step to ensure it remains a development-only tool without compilation errors in release builds.
Integrating StreamProbe into your existing Media3/ExoPlayer setup requires only a few lines of code.
Create an instance of StreamProbe and attach it right after building your player:
val streamProbe = StreamProbe()
// Inside your ViewModel, PlayerManager, or Activity where the player is created:
val player = ExoPlayer.Builder(context).build()
if (BuildConfig.DEBUG) {
streamProbe.attach(player)
}To capture per-segment TTFB estimates in the Segments tab, wrap your DataSource.Factory before passing it to DefaultMediaSourceFactory. This must be the outermost wrapper:
if (BuildConfig.DEBUG) {
val baseFactory: DataSource.Factory = DefaultHttpDataSource.Factory()
val mediaSourceFactory = DefaultMediaSourceFactory(context)
.setDataSourceFactory(streamProbe.wrapDataSourceFactory(baseFactory))
// Use mediaSourceFactory when building ExoPlayer
}Gate this behind BuildConfig.DEBUG in release-bound hosts.
In your Activity's onCreate(), call show(activity) to attach the draggable debug overlay to the view hierarchy. show() requires a ComponentActivity (which covers AppCompatActivity and Jetpack Compose's ComponentActivity):
// Works with AppCompatActivity, ComponentActivity, etc.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// UI setup (Compose or XML)
if (BuildConfig.DEBUG) {
streamProbe.show(this)
}
}
}Note: The overlay is lifecycle-aware and will automatically clean itself up when the Activity is destroyed. You don't need any special system overlay permissions; it runs entirely within your app's window.
When your player is released, don't forget to detach the probe to avoid memory leaks:
if (BuildConfig.DEBUG) {
streamProbe.detach()
}
player.release()The iOS API mirrors Android. Create a StreamProbe, attach it to your AVPlayer, install the built-in overlay window, and detach when you tear the player down. Gate everything behind #if DEBUG.
import StreamProbe
import AVFoundation
final class PlayerViewController: UIViewController {
private let probe = StreamProbe()
private var overlayWindow: UIWindow? // must be retained
private var player: AVPlayer!
override func viewDidLoad() {
super.viewDidLoad()
player = AVPlayer(url: streamURL)
#if DEBUG
probe.attach(player: player) // starts AVFoundation observation
probe.show() // starts live overlay updates
if let scene = view.window?.windowScene {
// Retain the returned window — a detached UIWindow with no strong
// reference is silently deallocated.
overlayWindow = probe.makeOverlayWindow(windowScene: scene)
}
#endif
}
deinit {
#if DEBUG
probe.detach() // stops observation, clears the session
overlayWindow = nil
#endif
}
}show() / hide() toggle the live data feed; the overlay window's isHidden controls UI visibility independently. To drive a custom renderer instead of the built-in window, observe probe.overlayPresenter.viewState (a SKIE AsyncSequence) directly. Call all methods from the main thread.
The overlay is rendered natively on each platform (Android Views, iOS UIKit) but driven by the same shared OverlayPresenter, so layout and behaviour are at parity. The sections below describe the full feature set; see Known Limitations for the iOS-specific deltas (per-segment metrics, CDN headers, and network timing require iOS 18+ and fall back to aggregate access-log roll-ups below it; DRM is not yet supported on iOS).
Once attached, the StreamProbe overlay appears as a draggable panel. It displays the currently active video track, active audio track, active subtitle track, DRM status (scheme · state · license latency, hidden for clear streams), the latest segment latency, and the CDN Cache state (Hit/Miss) at the top.
Four filter-chip views (Tracks / Segments / Switches / DRM) plus a dedicated Errors view reachable from the header indicator provide a complete picture of what the player is doing at any moment. The DRM chip is hidden automatically when no DRM events have been recorded.
This view displays all parsed renditions from the HLS .m3u8 or DASH .mpd manifest, grouped into three sections: VIDEO, AUDIO, and SUBTITLES.
- Video: Bandwidth, Resolution, and Codec for every video rendition.
- Audio: Language, channel layout, codec, and bitrate for every audio rendition. Muxed audio entries are marked with a
muxedbadge. - Subtitles: Language and type (WebVTT, TTML, CEA-608/708 CC) for every subtitle/caption rendition.
- The currently active rendition in each section is highlighted with an active-dot indicator in real-time.
Tracks the actual segment downloads as they happen. Each list item represents a segment chunk:
- Track-type badge & file extension: A compact color-coded pill next to the segment index marks the media kind —
Vvideo (blue),Aaudio (green),Ttext (purple) — so interleaved audio/video chunks are easy to tell apart and uneven download times read correctly. On Android the kind comes from Media3'sMediaLoadData.trackType; on iOS it stays unclassified (AVFoundation exposes no reliable per-segment media type), so the badge is hidden there. Alongside it, the segment's file extension (e.g.ts,aac,m4s), derived from the URI, renders on both platforms. - Download Duration & Size: See exactly how long a chunk took to fetch and its payload size.
- Throughput: Calculated bandwidth (Bytes / Duration) for that specific segment.
- TTFB: Time-to-first-byte. On Android it is a best-effort estimate (shown as
~NNms) that requires wrapping theDataSource.FactoryviaStreamProbe.wrapDataSourceFactory— rows without a correlated timing entry show nothing. On iOS 18+ it is read measured from the AVMetrics per-segment transaction (shown without the~, alongside DNS/connect/TLS). - CDN Status: A color-coded indicator (Green for Cache Hit, Red/Yellow for Cache Miss) based on captured CDN headers like
X-Cache,CF-Cache-Status, orX-Amz-Cf-Pop.
The segment list is capped at 500 entries; older entries are dropped automatically as new ones arrive.
A chronological timeline of every track switch ExoPlayer makes during the session, covering video, audio, and subtitle changes. Each entry is colour-coded by type: VID (blue), AUD (green), SUB (purple).
- From → To: The exact track properties before and after the switch. For subtitle disable events, the "To" side shows
Off. - Switch Reason: Indicates why the player changed tracks (e.g.,
ADAPTIVEdue to network conditions,MANUALby user selection,INITIALon startup). - Buffer State: Shows how many seconds of buffer were left when the switch occurred, which helps debug panic-driven drops.
The Switches log is capped at 200 entries; older events are dropped as new ones arrive.
A chronological timeline of DRM session lifecycle events for protected streams. The tab and its summary row are hidden automatically for clear (non-DRM) streams and appear as soon as the first DRM event is recorded.
- Event types and colour coding:
Session Acquired(blue),Keys Loaded(green),Session Released(grey),Error(cyan). - Scheme badge: each row shows a compact scheme label —
WV(Widevine),PR(PlayReady),CK(ClearKey), orDRM(unknown). - License latency:
Keys Loadedrows display the measured time from session acquire to key delivery (e.g.312ms). Latency may be inflated on key rotation events. - Summary row: the overlay header shows a live DRM line such as
Widevine · Keys Loaded · 312ms; cleared when the session is released.
The DRM event list is capped at 200 entries. DRM session manager errors are also forwarded to the Errors tab as DRM entries so all errors remain visible in one place.
A dedicated view for silent, non-fatal errors that ExoPlayer absorbs without triggering a fatal PlaybackException. Activated via the ⚠ N pill indicator in the overlay header.
⚠ Nheader indicator: appears as soon as the first error is captured, with a count of total errors. Tap to open the Errors view from any active tab.- Six captured categories:
LOAD(red) — segment or manifest HTTP errors (onLoadErrorforDATA_TYPE_MEDIA/DATA_TYPE_MANIFEST).CODEC(orange) — video codec failures (onVideoCodecError).FRAMES(yellow) — dropped video frame bursts ≥ 3 frames (onDroppedVideoFrames).AUDIO(purple) — audio sink errors (onAudioSinkError).ACODEC(green) — audio codec failures (onAudioCodecError).DRM(cyan) — DRM session manager errors (onDrmSessionManagerError); also surfaced in the DRM tab.
- Back / Clear / Share: the errors view header has a ← Back button to restore the previous tab, a Clear button to empty the list, and a ↗ Share button that fires an
ACTION_SENDintent with the full error list as plain text. - Inline expand: each row shows a
▾/▴chevron to signal tap-to-expand; tapping reveals the full URI, exception text, and absolute timestamp. - Dropped-frames dedup: consecutive
DROPPED_FRAMESevents within a 5-second window are merged into a single entry — the message updates to"X frames dropped (N bursts)"so slow devices don't flood the list. - The error list is capped at 200 entries; oldest entries are dropped when the cap is reached (except when the newest event merges into the last entry).
Coarse milestones. Each will be broken down into a TODO checklist as work begins.
-
M0 — Scaffolding ✅: Gradle module,
implementationdistribution with host-managed gating, emptyattach/detachAPI surface. -
M1 — HLS MVP ✅: Master playlist parsing, variant/rendition listing, basic overlay with active track display.
-
M2 — Segment & CDN ✅: Per-segment timing (total duration, size, throughput) and CDN response header capture with cache hit/miss flagging.
-
M3 — ABR Log ✅: Track switch event recording with buffer state, switch reason, and chronological timeline view in the overlay.
-
M4 — DASH Support ✅: DASH track enumeration via the Media3
TracksAPI, feature parity with HLS across all prior milestones. -
M5 — Distribution ✅: Published to Maven Central (
io.github.oguzhaneksi:streamprobe:0.3.2). -
M6 — Background Error Tracking ✅: Exposing silent, non-fatal background errors — segment load failures (HTTP 404/5xx), video codec errors (
onVideoCodecError), audio codec errors (onAudioCodecError), dropped frame bursts (onDroppedVideoFrames), and audio sink errors (onAudioSinkError) — as a real-time Errors view in the overlay, reachable via a header⚠ Nindicator. -
M7 — Audio & Subtitle Tracks ✅: Audio/subtitle rendition enumeration (HLS muxed sources included) + active audio/subtitle overlay; ABR switch events expanded to sealed
TrackSwitchEventcovering video, audio and subtitle switches. -
M8 — DRM Monitoring ✅: DRM session lifecycle tracking (Widevine, PlayReady, ClearKey) via a dedicated
DrmSessionTrackeranalytics listener. Captures session acquire/release events, license key load latency, and DRM manager errors. A live DRM summary row appears in the overlay header; a DRM chip reveals a chronological DRM event timeline. DRM errors are dual-surfaced in both the DRM tab and the Errors tab. -
M9 — TTFB & Advanced Network Metrics (baseline) ✅: Best-effort TTFB capture via a
DataSource.Factorywrapper (StreamProbe.wrapDataSourceFactory). Theopen()-duration proxy approximates TTFB and is correlated to each media segment by request URI + byte position; shown in the Segments tab as~NNms. Per-phase DNS/connect/TLS breakdown andNetworkInspector/OkHttp/Cronet/HttpEngine adapters are deferred. -
Kotlin Multiplatform + iOS ✅: Migrated the SDK to Kotlin Multiplatform — the pure core (
SessionStore, models,OverlayPresenter, formatters, parsers, registries) moved tocommonMainand is shared verbatim with Android. Added iOS targets and a native SwiftAVPlayeradapter that renders the full draggable overlay in UIKit (orientation-aware layout, drag, expandable rows, auto-scroll, share — at parity with Android). iOS ships as a two-layer Swift Package: a KotlinStreamProbeCorebinary XCFramework + a SwiftStreamProbelayer, bridged by SKIE, distributed via SPM (versioned independently of Android, starting at0.1.0). Android's published public API and Maven Central flow are unchanged. -
iOS 18 per-segment metrics ✅: On iOS 18+, consume AVFoundation's
AVMetricHLSMediaSegmentRequestEventstream (AVMetricsSegmentAdapter→ the commonavMetricsSegmentMetricmapper) for true per-segment download metrics — duration, size, throughput, real CDN response headers (cache hit/miss), and measured network timing (TTFB/DNS/connect/TLS) — bringing the iOS Segments tab to Android parity. iOS 17 and earlier continue to derive aggregate roll-ups from theAVPlayerItemaccess log. The access-log path also processes bitrate switches and dropped-frame bursts immediately (including the live tail) so the overlay no longer lags one notification behind for those events. -
iOS live HLS (demo) ✅: The SwiftUI demo gained live/DVR HLS support — a
LIVEbadge, a DVR-window scrubber, and live entries in the stream catalog — exercising the SDK against live streams. -
Segment track-type badges ✅: Each captured segment is classified as
VIDEO/AUDIO/TEXT/UNKNOWN(a newSegmentTrackTypeon the sharedSegmentMetric) and surfaced in the Segments timeline as a color-codedV/A/Tpill plus the segment's file extension. Android fills the track type from Media3'sMediaLoadData.trackType; iOS leaves itUNKNOWN(badge hidden) while the URI-derived extension still renders on both platforms. The track type + extension also appear on the latest-segment stat line. -
M10 — SSAI & Timeline Metadata (Planned): Listening to
onMetadatafor SCTE-35 and ID3 tags to visually distinguish Server-Side Ad Insertion (SSAI) ad breaks from main content. -
M11 — Advanced Network Metrics (per-phase) (Planned): True per-phase DNS/connect/TLS breakdown via a
NetworkInspectorabstraction supporting OkHttp, Cronet, and HttpEngine adapters. -
M12 — QoS Stall Diagnosis (Planned): Treat playback stalls as a QoS signal, not a QoE one — when playback rebuffers, classify the root cause from the delivery layer rather than just recording that a stall happened. Each stall is diagnosed as
SEGMENT_LOAD_FAILURE(a segment/manifest fetch failed just before the stall),BANDWIDTH_STARVATION(measured throughput below the selected rendition bitrate),THROUGHPUT_HEALTHY(network fine → cause is upstream: manifest discontinuity / buffer policy), orUNKNOWN, and surfaced in the Errors view as aSTALLentry carrying the verdict plus the throughput-vs-selected evidence. Stall detection is wired on both the Android (STATE_BUFFERINGafterSTATE_READY, seek-suppressed) and iOS (AVPlayerItemPlaybackStalledNotification) players, sharing a single cross-platform classifier. Also signals segment-cardinality honestly for the pre-iOS-18 access-log path (which yields rolling roll-ups, not true per-segment data — iOS 18+ already records per-segment metrics via AVMetrics).
- Multi-period DASH: ExoPlayer merges tracks from all Periods into a single
Tracksobject. If the same representation appears in multiple Periods (e.g., around ad boundaries), it may be listed more than once. Period-aware grouping is a planned future enhancement. - Audio-Only Streams: StreamProbe currently focuses heavily on video variant and segment analysis. Audio-only HLS/DASH streams are not officially supported yet and may yield incomplete track or ABR logs.
The Android implementation hooks Media3 at the analytics/HTTP layer; the iOS implementation observes AVFoundation, which exposes a coarser surface. As a result some features differ on iOS:
- Per-segment metrics require iOS 18+. On iOS 18+ StreamProbe consumes AVFoundation's
AVMetricHLSMediaSegmentRequestEventstream and records true per-segment download duration, size, and throughput — at parity with Android. On iOS 17 and earlier AVFoundation exposes no per-segment surface, so metrics are derived fromAVPlayerItemaccess-log events, which are rolling roll-ups over the session rather than individual segment downloads. - CDN headers and TTFB require iOS 18+. The iOS 18 AVMetrics per-segment events carry the final transaction's HTTP response headers and
URLSessionTaskTransactionMetricstiming, so the CDN cache hit/miss indicator and measured network timing (TTFB/DNS/connect/TLS) light up on iOS 18+. On iOS 17 and earlier the access-log roll-ups expose neither headers nor per-request timing, so those indicators are unavailable there (the Android~NNmsTTFB estimate has no equivalent below iOS 18). - DRM monitoring is Android-only. The DRM lifecycle timeline (Widevine / PlayReady / ClearKey) relies on Media3's
DrmSessionManagercallbacks. iOS uses FairPlay, which has no equivalent observation surface today — see below. - No item-change tracking. The iOS probe scopes its observers to the player's
currentItem.AVQueuePlayeritem transitions (advancing to the next queued item) are not yet tracked. - Simulator TLS. Live HTTPS playback fails inside the sandboxed iOS Simulator (its network daemon can't complete TLS handshakes). This is a Simulator limitation, not an SDK defect — verify live behaviour on a real device.
FairPlay DRM monitoring on iOS is not yet supported and is planned for evaluation in a future phase. It is gated on external FairPlay license/key-server infrastructure, which is required to meaningfully observe and verify the session lifecycle. Until then, DRM diagnostics are available on Android only.
StreamProbe ships with an empty consumer-rules.pro. No additional keep rules are required in your own ProGuard/R8 configuration — Media3 and AndroidX ship their own consumer rules, and StreamProbe's public API surface relies only on those libraries and standard Android APIs.
If you are using R8 in full-mode and notice issues, please open an issue with the relevant diagnostic output.
The repository includes a demo application for each platform that shows a complete, real-world integration.
Android (app/ module):
- Streams are selected from a pre-populated list (HLS and DASH sources).
- The
StreamProbeinstance lives in aViewModel, withattach()called after the player is built andshow()called fromActivity.onCreate(). detach()is called inViewModel.onCleared(), ensuring the session is torn down when the player is released.
Clone the repository and open it in Android Studio to run the demo directly.
iOS (iosApp/ — SwiftUI):
- An HLS stream catalog (VOD and live entries) with a fullscreen
AVPlayerscreen and a settings screen (overlay toggle, auto-play, loop). - Live streams render a
LIVEbadge and a DVR-window scrubber so live/DVR playback can be exercised against the SDK. - Consumes the
StreamProbeSwift package as a local SPM dependency and installs the overlay window viamakeOverlayWindow(windowScene:). - The Xcode project is generated by XcodeGen. Run
./gradlew :sdk:assembleStreamProbeCoreDebugXCFrameworkand regenerate the project withiosApp/generate.shbefore opening in Xcode.
We welcome contributions from the community! If you've found a bug, want to add a feature, or improve documentation, follow these standard open-source steps:
- Fork the repository and clone it locally.
- Create a branch for your feature or bugfix (
git checkout -b feature/awesome-new-tool). - Commit your changes with clear, descriptive messages.
- Push your branch to your fork (
git push origin feature/awesome-new-tool). - Open a Pull Request against the
mainbranch.
Please ensure any changes pass existing tests or add new ones where applicable.
For the detailed feature breakdown, API surface, and technical design, see SPEC.md.
Apache License 2.0. See LICENSE for details.