the audio files generated by livekit of recorded p...
# ask-ai
c
the audio files generated by livekit of recorded phone calls have a wrong meta tag of duration . Example, an audio of 2 minutes, the meta tag define 4 minutes and it have impact in our UI when we put a player to reproduce the audio. Is this a known error?
t
Thanks for reaching out, Mauricio. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m sorry this is causing trouble in your UI. You’re asking whether this incorrect duration metadata on LiveKit-generated call recordings is a known issue. Short answer: This is not documented as a known LiveKit bug. LiveKit’s egress produces audio (e.g., Ogg for Opus) and a JSON manifest by default, but there’s no explicit note that embedded duration tags can be wrong. What can cause this and how to troubleshoot • Container/codec behaviour: VBR encoding or incomplete frame finalization can make container-level duration tags differ from the real playback length. • Manifest vs embedded tag: The egress JSON manifest contains metadata; the file’s internal duration tag and the manifest can disagree. • Player interpretation: Some audio players rely on the embedded tag rather than reading actual samples/frames, producing the UI issue. • Post-processing: Any processing step after egress (transcoding, concatenation) can alter or leave incorrect duration tags. Quick steps you can try 1. Compare durations: ◦ Check the egress JSON manifest duration (if present). ◦ Inspect actual media duration with a media tool (ffprobe/mediainfo) and compare to the embedded tag your player uses. 1. Re-mux or normalize the file: ◦ Use ffmpeg to re-multiplex the file without re-encoding:
ffmpeg -i input.ogg -c copy output.ogg
This often corrects container duration tags. 1. Re-encode if needed: ◦ If re-muxing doesn’t help, re-encode to a fresh file to ensure correct timestamps. 1. Use manifest or measured duration in UI: ◦ If your player trusts embedded tags, consider using the egress manifest duration or programmatically measuring duration from the audio samples as the source of truth. If you want, I can help with next steps — for example: • Tell me whether your recordings include the egress JSON manifest. • Share one anonymized file (or its ffprobe/mediainfo output) and I can suggest an ffmpeg command or pinpoint whether the manifest or the container tag is the mismatch. Sources: Track egress | LiveKit docs | Egress API | LiveKit docs
e
How can i confirm that agent is seeing the current shared screen correctly, how would i know ?
t
Thanks — I’m responding to your question about how to confirm an agent is seeing the current shared screen. You can’t directly read “what the agent is seeing,” but you can reliably confirm they are receiving and rendering the shared screen by these checks: 1. Verify the agent subscribed to the screen-share track • Listen for the
track_subscribed
event and check
publication.source
is
SCREEN_SHARE
and
kind
is
video
. • If subscribed, the agent should be able to receive frames. 1. Confirm token/permissions allow subscription • Ensure the agent’s token grant includes
canSubscribe: true
(and room join) for video. 1. Confirm frames are being received/processed • On the agent side, log or count incoming video frames (or use the SDK video renderer callback). Active frame delivery shows the agent is receiving the screen share. • Example: when you get video frames from the track, increment a counter or timestamp the last frame — if updates stop, the agent is no longer seeing new content. 1. Use the egress or app manifest/metadata as a fallback • If you record the session or produce a manifest, compare that to what the agent subscribes to as an additional check. 1. UI-level confirmation (optional) • Add an in-app indicator driven by subscription + recent-frame activity (e.g., “Screen share: receiving” vs “no frames in last 5s”). This provides a user-visible confirmation without claiming to know exactly what the agent sees. If you’d like, tell me: • Which SDK/language the agent uses (Web, iOS, Android, or native server SDK), and • Whether you prefer a code example for detecting
track_subscribed
or for counting frames — and I’ll provide a short, targeted snippet. Sources: Job lifecycle | LiveKit docs | Subscribing to tracks | LiveKit docs | Stream import | LiveKit docs | Rooms, participants, and tracks | LiveKit docs | Authentication | LiveKit docs
e
I am using python sdk, give me code snippets to verify these claims
t
Thanks — responding with Python SDK snippets to: • detect subscription to a screen-share track, • confirm the track source/kind, • count/monitor incoming video frames (confirm rendering/receipt), • and expose a simple "recent-frame" status you can surface in UI. 1) Listen for track_subscribed and check source/kind
Copy code
# assumes an existing Room connection `room` from livekit SDK
from livekit import TrackSource, RemoteTrackPublication

def on_track_subscribed(track, publication: RemoteTrackPublication, participant):
    # publication.source will be a TrackSource (e.g., SCREEN_SHARE)
    print("track subscribed:", publication.sid, "source:", publication.source, "kind:", publication.kind)
    if publication.source == TrackSource.SCREEN_SHARE and publication.kind == "video":
        print(f"Subscribed to screen share from {participant.identity}")
        # start monitoring frames for this track
        monitor_video_track(track, publication)

room.on("track_subscribed", on_track_subscribed)
2) Monitor video frames and keep a last-frame timestamp / counter
Copy code
import asyncio
import time
from collections import defaultdict

# store state per publication.sid
frame_state = defaultdict(lambda: {"count": 0, "last_ts": 0.0})

def monitor_video_track(track, publication):
    # track is a RemoteVideoTrack (async frame iterator)
    sid = publication.sid

    async def reader():
        async for frame in track:  # iterates VideoFrame objects
            frame_state[sid]["count"] += 1
            frame_state[sid]["last_ts"] = time.time()
            # optional: small validation, e.g., non-empty frame
            if frame.width == 0 or frame.height == 0:
                print("Received empty frame (possible issue)")
            # if you render to a UI, your renderer callback would also confirm display
            # e.g., render_frame_to_ui(frame)
    asyncio.create_task(reader())
3) Expose a simple status check you can call from your UI/backend
Copy code
def screen_share_status(publication_sid, stale_seconds=5.0):
    s = frame_state.get(publication_sid)
    if not s:
        return {"status": "no_subscription", "count": 0, "last_ts": None}
    age = time.time() - s["last_ts"]
    if age < stale_seconds:
        return {"status": "receiving", "count": s["count"], "last_ts": s["last_ts"]}
    else:
        return {"status": "stale", "count": s["count"], "last_ts": s["last_ts"]}
4) Example token permission check (server-side when generating token) • Ensure the agent token grants
roomJoin: true
and
canSubscribe: true
for video. Example grant JSON shape you should include when issuing tokens:
Copy code
{
  "video": {
    "room": "myroom",
    "roomJoin": true,
    "canSubscribe": true
  }
}
Notes and next steps • The
track_subscribed
event + active frame iteration is the reliable programmatic confirmation that the agent is receiving the screen-share video. Use
screen_share_status
to show a UI indicator (receiving / stale / no_subscription). • If you want, tell me which Python LiveKit package version you’re using and I’ll tailor the snippet to that SDK surface or provide a runnable minimal example.
e
is user turn completed data hook available in gemini realtime, or do i need to enable the vad for that ?
t
Thanks — you’re asking whether the
on_user_turn_completed
hook is available in Gemini Realtime or if you must enable VAD. Short answer: The hook is available, but it will only fire if LiveKit (not Gemini’s automatic activity detection) is performing turn detection. That means you must disable Gemini’s automatic activity detection and enable a LiveKit turn detector — either a VAD plugin (e.g.,
silero.VAD
) or the LiveKit turn detection model (which requires an STT plugin). VAD is not strictly required, but some form of LiveKit-side turn detection must be enabled for
on_user_turn_completed
to trigger. Minimal Python example:
Copy code
from livekit.agents import AgentSession
from livekit.plugins import silero, turn_detector, google

session = AgentSession(
    turn_detection=turn_detector.MultilingualModel(),  # or use VAD
    vad=silero.VAD.load(),                             # required if using VAD
    llm=google.realtime.RealtimeModel(
        model="gemini-2.5-flash-native-audio-preview-09-2025",
        realtime_input_config=google.types.RealtimeInputConfig(
            automatic_activity_detection=google.types.AutomaticActivityDetection(disabled=True),
        ),
        input_audio_transcription=None,
    ),
    stt=google.stt.STT(),  # required if using the turn detection model
)
If you’d like, tell me which turn-detection approach you prefer (VAD vs turn-detector model) and I’ll give a focused setup and example for hooking
on_user_turn_completed
. Sources: Turns overview | LiveKit docs | Gemini Live API plugin | LiveKit docs | Pipeline nodes and hooks | LiveKit docs