Skip to content

Ya/replace rtmp with srt#2246

Open
yagarwal1307 wants to merge 16 commits intodevfrom
ya/replace-rtmp-with-srt
Open

Ya/replace rtmp with srt#2246
yagarwal1307 wants to merge 16 commits intodevfrom
ya/replace-rtmp-with-srt

Conversation

@yagarwal1307
Copy link
Copy Markdown
Member

@yagarwal1307 yagarwal1307 commented Mar 19, 2026

  • generalise streaming infra away from rtmp
  • Implement SRT streaming on glasses
  • Implement webrtc streaming on glasses
  • Implement signed URLs to watch the webrtc stream

@yagarwal1307 yagarwal1307 requested a review from a team as a code owner March 19, 2026 14:33
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 19, 2026

📋 PR Review Helper

📱 Mobile App Build

Ready to test! (commit d7f4ed9)

📥 Download APK

🕶️ ASG Client Build

Ready to test! (commit d7f4ed9)

📥 Download ASG APK


🔀 Test Locally

gh pr checkout 2246

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 19, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee7666a6-572f-4f4c-9451-d287dbc1fc04

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ya/replace-rtmp-with-srt

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@yagarwal1307 yagarwal1307 changed the base branch from main to dev March 19, 2026 14:33
@gitguardian
Copy link
Copy Markdown

gitguardian bot commented Mar 19, 2026

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@aisraelov
Copy link
Copy Markdown
Member

aisraelov commented Mar 23, 2026

Hey awesome work so far. A few things to note:

  1. rtmpUrl/srtUrl confusion in CloudflareStreamService

Right now createLiveInput() computes the real RTMP URL into a local variable, then throws it away and stuffs the SRT URL into the rtmpUrl field:

const rtmpUrl = ${liveInput.rtmps.url}${liveInput.rtmps.streamKey}; // computed, never used

  const result: LiveInputResult = {
    rtmpUrl: srtUrl!,  // ← this is an SRT URL, not RTMP
    srtUrl,
  };

This means managed streaming can never send an actual RTMP URL anymore. Every other part of the code reads rtmpUrl expecting RTMP but gets srt://.... which works but isn't documented well.

  1. WebRtcStreamingService isn't wired up

Here's how I'd finish the PR (probably just feed this to claude and it can 1shot... lmk what you think):

Streaming Protocol Refactor: Unified Multi-Protocol Support

Context

The current streaming pipeline was built entirely around RTMP — every field is named rtmpUrl, every message type is START_RTMP_STREAM, every handler is RtmpCommandHandler. PR #2246 tries to add SRT and WebRTC support by stuffing SRT URLs into the rtmpUrl field, which works by accident but creates a fragile, confusing system.

We need to properly support 5 streaming modes:

  • Livestream (managed) — SRT to Cloudflare under the hood, Cloudflare restreams via RTMP to YouTube/Twitch/etc, provides HLS/DASH playback
  • Direct RTMP — direct RTMP to user's endpoint
  • Direct SRT — direct SRT to user's endpoint
  • Direct WebRTC — direct WHIP to user's endpoint
  • Livestream WebRTC (managed) — WHIP to Cloudflare's WebRTC endpoint

WebRTC runs only on asg_client (Android glasses). Phone is command passthrough only.

This is a clean break. All RTMP-specific naming is replaced. No deprecated aliases, no backwards compat shims.

SDK Public API

// Direct streaming — you provide the URL, glasses push directly to it
session.camera.startDirectStream({
  streamUrl: "rtmp://your-server.com/live/key",   // or srt:// or https:// (WHIP)
  video?: VideoConfig,
  audio?: AudioConfig,
  sound?: boolean,
})

// Livestreaming — Cloudflare handles infra, restreaming, HLS/DASH playback
session.camera.startLivestream({
  quality?: "720p" | "1080p",
  enableWebRTC?: boolean,        // use WHIP to Cloudflare instead of SRT
  restreamDestinations?: [       // Cloudflare restreams to these
    { url: "rtmp://youtube.com/live/KEY", name: "YouTube" },
    { url: "rtmp://twitch.tv/app/KEY", name: "Twitch" },
  ],
  video?: VideoConfig,
  audio?: AudioConfig,
  sound?: boolean,
})
// Returns: { hlsUrl, dashUrl, webrtcPlaybackUrl?, streamId }

session.camera.stopDirectStream()
session.camera.stopLivestream()

Design Principle

The rtmpUrl field was always a generic "stream ingest URL." Rather than creating parallel pipelines for each protocol, we generalize the existing pipeline: rename the field, detect protocol from the URL prefix, and let the glasses-side router pick the right streamer.

Architecture

SDK App
  │
  ├─ startDirectStream({ streamUrl: "rtmp://..." })  ← direct RTMP
  ├─ startDirectStream({ streamUrl: "srt://..." })   ← direct SRT
  ├─ startDirectStream({ streamUrl: "https://..." }) ← direct WebRTC (WHIP)
  ├─ startLivestream({ restreamDestinations: [...] })← livestream (cloud picks protocol)
  │
  ▼
Cloud (DirectStreamingExtension or LivestreamExtension)
  │  validates URL prefix, creates lifecycle
  │
  ▼
WebSocket message: start_stream { streamUrl, protocol, streamId, ... }
  │
  ▼
Mobile SocketComms (passthrough) → CoreModule.startStream(params)
  │
  ▼
asg_client: StreamCommandHandler routes by protocol field
  ├─ "rtmp" / "rtmps" → RtmpStreamingService (StreamPackLite RTMP)
  ├─ "srt"            → RtmpStreamingService (StreamPackLite SRT mode)
  └─ "webrtc"         → WebRtcStreamingService (WHIP)

Livestream (Managed) Flow

Glasses ──[SRT]──> Cloudflare Live Input ──> HLS/DASH playback URLs (for apps/web)
                                           ├─> Output: rtmp://youtube.com/live/KEY
                                           ├─> Output: rtmp://twitch.tv/app/KEY
                                           └─> Output: rtmps://facebook.com/rtmp/KEY

Cloud picks ingest protocol (SRT by default, WHIP if enableWebRTC). Cloudflare handles all restreaming. Restream destinations, createOutputs(), OutputStatus, and the REST API for managing outputs all stay exactly as they are.

Changes by Layer

Layer 1: SDK Types (cloud/packages/sdk/src/types/)

message-types.ts — Replace RTMP message types:

// Replace these:
//   START_RTMP_STREAM = "start_rtmp_stream"
//   STOP_RTMP_STREAM = "stop_rtmp_stream"
//   KEEP_RTMP_STREAM_ALIVE = "keep_rtmp_stream_alive"
// With:
START_STREAM = "start_stream",
STOP_STREAM = "stop_stream",
KEEP_STREAM_ALIVE = "keep_stream_alive",

Update all references throughout the codebase.

messages/cloud-to-glasses.ts — Replace StartRtmpStream with:

type StreamProtocol = "rtmp" | "rtmps" | "srt" | "webrtc";

interface StartStream extends BaseMessage {
  type: CloudToGlassesMessageType.START_STREAM;
  streamUrl: string;        // ingest URL (rtmp://, srt://, https://...whip)
  protocol: StreamProtocol;  // explicit protocol
  appId: string;
  streamId?: string;
  video?: any;
  audio?: any;
  stream?: any;
  flash?: boolean;
  sound?: boolean;
}

interface StopStream extends BaseMessage {
  type: CloudToGlassesMessageType.STOP_STREAM;
  appId: string;
  streamId?: string;
}

interface KeepStreamAlive extends BaseMessage {
  type: CloudToGlassesMessageType.KEEP_STREAM_ALIVE;
  streamId: string;
  ackId: string;
}

Delete StartRtmpStream, StopRtmpStream, KeepRtmpStreamAlive.

messages/glasses-to-cloud.ts — Replace RtmpStreamStatus with:

// Replace RTMP_STREAM_STATUS = "rtmp_stream_status" with:
STREAM_STATUS = "stream_status"

Rename the interface from RtmpStreamStatusStreamStatus. Same payload.

messages/app-to-cloud.ts — Rename request types:

// Replace RtmpStreamRequest with:
interface DirectStreamRequest {
  packageName: string;
  streamUrl: string;  // was rtmpUrl
  video?: VideoConfig;
  audio?: AudioConfig;
  stream?: StreamConfig;
  sound?: boolean;
}

// Replace ManagedStreamRequest with:
interface LivestreamRequest {
  packageName: string;
  quality?: "720p" | "1080p";
  enableWebRTC?: boolean;
  restreamDestinations?: RestreamDestination[];  // unchanged
  video?: VideoConfig;
  audio?: AudioConfig;
  stream?: StreamConfig;
  sound?: boolean;
}

Layer 2: SDK Camera Module (cloud/packages/sdk/src/app/session/modules/camera.ts)

Replace public API:

// Direct streaming (was startRtmpStream)
interface DirectStreamOptions {
  streamUrl: string;  // rtmp://, rtmps://, srt://, or https:// (WHIP)
  video?: VideoConfig;
  audio?: AudioConfig;
  stream?: StreamConfig;
  sound?: boolean;
}

async startDirectStream(options: DirectStreamOptions): Promise<void>
async stopDirectStream(): Promise<void>
isDirectStreaming(): boolean
getDirectStreamStatus(): StreamStatus | undefined
onDirectStreamStatus(handler: StreamStatusHandler): () => void

// Livestreaming (was startManagedStream)
interface LivestreamOptions {
  quality?: "720p" | "1080p";
  enableWebRTC?: boolean;
  restreamDestinations?: RestreamDestination[];
  video?: VideoConfig;
  audio?: AudioConfig;
  stream?: StreamConfig;
  sound?: boolean;
}

interface LivestreamResult {
  hlsUrl?: string;
  dashUrl?: string;
  webrtcPlaybackUrl?: string;
  previewUrl?: string;
  streamId: string;
}

async startLivestream(options?: LivestreamOptions): Promise<LivestreamResult>
async stopLivestream(): Promise<void>
isLivestreamActive(): boolean
getLivestreamUrls(): LivestreamResult | undefined
onLivestreamStatus(handler: (status: LivestreamStatus) => void): () => void

Delete RtmpStreamOptions, startRtmpStream(), ManagedStreamOptions, startManagedStream().

camera-managed-extension.ts — Rename file to camera-livestream-extension.ts. Rename class/types accordingly.

Layer 3: Cloud Extensions

CloudflareStreamService.ts — Fix LiveInputResult:

interface LiveInputResult {
  liveInputId: string;
  rtmpUrl: string;              // actual RTMP URL (rtmps://...)
  srtUrl?: string;              // SRT URL if available
  whipUrl?: string;             // WHIP URL if available (from liveInput.webRTC.url)
  hlsUrl: string;
  dashUrl: string;
  webrtcPlaybackUrl?: string;   // playback URL
}

Return ALL URLs from Cloudflare. Let the caller pick. No more rtmpUrl: srtUrl! hack.

Rename ManagedStreamingExtension.tsLivestreamExtension.ts:

  • Class: LivestreamExtension
  • Choose protocol and send StartStream:
    • Default: use srtUrl for ingest (better for unstable connections)
    • If enableWebRTC is true AND whipUrl available: use WHIP
    • Fallback to rtmpUrl if SRT not available
    • Send StartStream with streamUrl + protocol

Rename UnmanagedStreamingExtension.tsDirectStreamingExtension.ts:

  • Class: DirectStreamingExtension
  • Detect protocol from URL:
    • rtmp:// → protocol "rtmp"
    • rtmps:// → protocol "rtmps"
    • srt:// → protocol "srt"
    • https:// → protocol "webrtc" (WHIP)
  • Rename internal field from rtmpUrl to streamUrl
  • Send StartStream message

Rename ManagedStreamStatusLivestreamStatus in cloud-to-app messages.

Layer 4: Mobile Bridge

SocketComms.ts — Replace handlers:

// Replace handle_start_rtmp_stream with:
private handle_start_stream(msg: any) {
  const streamUrl = msg.streamUrl || ""
  if (streamUrl) {
    CoreModule.startStream(msg)
  } else {
    console.log("Invalid stream request: missing streamUrl")
  }
}

// Replace handle_stop_rtmp_stream with:
private handle_stop_stream(msg: any) {
  CoreModule.stopStream()
}

// Replace handle_keep_rtmp_stream_alive with:
private handle_keep_stream_alive(msg: any) {
  CoreModule.keepStreamAlive(msg)
}

Update the message type routing map to use start_stream, stop_stream, keep_stream_alive.

Replace sendRtmpStreamStatus()sendStreamStatus().

CoreModule.ts — Replace methods:

startStream(params: Record<string, any>): Promise<void>
stopStream(): Promise<void>
keepStreamAlive(params: Record<string, any>): Promise<void>

CoreModule.kt — Replace bridge functions:

AsyncFunction("startStream") { params: Map<String, Any> ->
  coreManager?.startStream(params.toMutableMap())
}
AsyncFunction("stopStream") {
  coreManager?.stopStream()
}
AsyncFunction("keepStreamAlive") { params: Map<String, Any> ->
  coreManager?.keepStreamAlive(params.toMutableMap())
}

CoreManager.kt — Replace delegation:

fun startStream(message: MutableMap<String, Any>) {
  sgc?.startStream(message)
}
fun stopStream() {
  sgc?.stopStream()
}
fun keepStreamAlive(message: MutableMap<String, Any>) {
  sgc?.keepStreamAlive(message)
}

Layer 5: asg_client (Glasses)

Replace RtmpCommandHandler.java with StreamCommandHandler.java:

  • Handles start_stream, stop_stream, keep_stream_alive
  • Reads protocol field from message
  • Routes to RtmpStreamingService (for rtmp/rtmps/srt) or WebRtcStreamingService (for webrtc)
  • Falls back to URL prefix detection if protocol field missing

RtmpStreamingService.java — Rename mRtmpUrlmStreamUrl, setRtmpUrl()setStreamUrl(). Already supports SRT via StreamPackLite internally.

WebRtcStreamingService.java — Already written in PR #2246. Wire into StreamCommandHandler routing.

ServiceConstants.java — Replace:

COMMAND_START_STREAM = "start_stream"
COMMAND_STOP_STREAM = "stop_stream"
COMMAND_KEEP_STREAM_ALIVE = "keep_stream_alive"

Layer 6: Status Reporting

Status message type: Replace RTMP_STREAM_STATUSSTREAM_STATUS ("stream_status").

Status flows back through same path:

asg_client StreamingStatusCallback
  → WebSocket → cloud glasses-message-handler
    → handleStreamStatus()
      → appManager.sendMessageToApp()

Rename sendRtmpStatusResponsesendStreamStatusResponse in asg_client.

Files to Modify

File Change
cloud/packages/sdk/src/types/message-types.ts Replace START_RTMP_STREAM → START_STREAM, etc.
cloud/packages/sdk/src/types/messages/cloud-to-glasses.ts Replace StartRtmpStream → StartStream
cloud/packages/sdk/src/types/messages/glasses-to-cloud.ts Replace RtmpStreamStatus → StreamStatus
cloud/packages/sdk/src/types/messages/app-to-cloud.ts RtmpStreamRequest → DirectStreamRequest, ManagedStreamRequest → LivestreamRequest
cloud/packages/sdk/src/types/messages/cloud-to-app.ts ManagedStreamStatus → LivestreamStatus
cloud/packages/sdk/src/app/session/modules/camera.ts startRtmpStream → startDirectStream, startManagedStream → startLivestream
cloud/packages/sdk/src/app/session/modules/camera-managed-extension.ts Rename to camera-livestream-extension.ts
cloud/packages/cloud/src/services/streaming/CloudflareStreamService.ts Fix LiveInputResult, return all URLs
cloud/packages/cloud/src/services/streaming/ManagedStreamingExtension.ts Rename to LivestreamExtension.ts, use StartStream
cloud/packages/cloud/src/services/session/UnmanagedStreamingExtension.ts Rename to DirectStreamingExtension.ts, use StartStream
cloud/packages/cloud/src/services/session/handlers/glasses-message-handler.ts Rename handler, use stream_status
cloud/packages/cloud/src/services/streaming/StreamRegistry.ts Update field names
mobile/src/services/SocketComms.ts Replace handle_start_rtmp_stream → handle_start_stream
mobile/modules/core/src/CoreModule.ts Replace startRtmpStream → startStream
mobile/modules/core/android/.../CoreModule.kt Replace bridge functions
mobile/modules/core/android/.../CoreManager.kt Replace delegation methods
asg_client/.../handlers/StreamCommandHandler.java New file, replaces RtmpCommandHandler
asg_client/.../handlers/RtmpCommandHandler.java Delete
asg_client/.../services/RtmpStreamingService.java Rename mRtmpUrl → mStreamUrl
asg_client/.../services/WebRtcStreamingService.java Wire into StreamCommandHandler
asg_client/.../utils/ServiceConstants.java Replace RTMP constants

Verification

  1. SDK build: cd cloud && bun run build — verify no type errors
  2. Cloud lint: cd cloud/packages/cloud && bun run lint
  3. Cloud tests: cd cloud && bun run test
  4. asg_client build: cd asg_client && ./gradlew assembleDebug
  5. Grep for remnants: rg "rtmpUrl|startRtmpStream|START_RTMP|RTMP_STREAM|ManagedStream|UnmanagedStream" --type ts --type java --type kotlin — should return zero hits outside of comments/docs
  6. Manual: Test each protocol with asg_client test scripts

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 23, 2026

Deploying mentra-live-ota-site with  Cloudflare Pages  Cloudflare Pages

Latest commit: c4f179f
Status: ✅  Deploy successful!
Preview URL: https://c6411b9f.mentra-live-ota-site.pages.dev
Branch Preview URL: https://ya-replace-rtmp-with-srt.mentra-live-ota-site.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 23, 2026

Deploying mentra-store-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: c4f179f
Status: ✅  Deploy successful!
Preview URL: https://47f5953d.augmentos-appstore-2.pages.dev
Branch Preview URL: https://ya-replace-rtmp-with-srt.augmentos-appstore-2.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 23, 2026

Deploying dev-augmentos-console with  Cloudflare Pages  Cloudflare Pages

Latest commit: c4f179f
Status: ✅  Deploy successful!
Preview URL: https://567928ff.dev-augmentos-console.pages.dev
Branch Preview URL: https://ya-replace-rtmp-with-srt.dev-augmentos-console.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 23, 2026

Deploying prod-augmentos-account with  Cloudflare Pages  Cloudflare Pages

Latest commit: c4f179f
Status: ✅  Deploy successful!
Preview URL: https://5817ebf0.augmentos-e84.pages.dev
Branch Preview URL: https://ya-replace-rtmp-with-srt.augmentos-e84.pages.dev

View logs

@yagarwal1307 yagarwal1307 force-pushed the ya/replace-rtmp-with-srt branch from 71afd75 to 0bf1250 Compare March 27, 2026 07:21
@aisraelov
Copy link
Copy Markdown
Member

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4f179faa2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

type: CloudToGlassesMessageType.START_STREAM,
sessionId: userSession.sessionId,
rtmpUrl: liveInput.rtmpUrl, // Cloudflare ingest URL
streamUrl: liveInput.srtUrl!, // Cloudflare ingest URL
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard against missing SRT ingest URL before start

startManagedStream always sends streamUrl: liveInput.srtUrl!, but CloudflareStreamService.createLiveInput() makes srtUrl optional and can return undefined when Cloudflare does not provide SRT fields. In that case we send an invalid START_STREAM payload to glasses, so managed streaming fails to start even though an ingest URL (rtmpUrl) is available. Add an explicit null check with fallback/error handling before building the start message.

Useful? React with 👍 / 👎.

Comment on lines +288 to +289
String streamId = getActiveStreamId();
if (streamId != null && !streamId.isEmpty()) status.put("streamId", streamId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use source service stream IDs in status callbacks

The shared callback tags status events with getActiveStreamId(), which picks whichever service currently has an ID (RTMP, then SRT, then WHIP). During protocol switches, a delayed stop/error callback from the previous service can therefore be emitted with the new stream's ID, causing cloud-side state to mark the wrong stream as stopped/errored. Stream IDs should come from the emitting service (or per-service callbacks), not global active-service lookup.

Useful? React with 👍 / 👎.

Comment on lines +190 to +191
} else if (WhipStreamingService.isStreaming()) {
WhipStreamingService.stopStreaming(context);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop WHIP sessions even when reconnecting

handleStopCommand only stops WHIP when WhipStreamingService.isStreaming() is true, but that method returns false during reconnecting/starting states. If a user sends stop_stream while WHIP is reconnecting, the handler reports not_streaming and leaves the reconnect loop running, so the stream can come back unexpectedly. Include reconnecting/starting states (or stop unconditionally) for WHIP stop handling.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants