Compare commits

...

4 Commits

Author SHA1 Message Date
af921ce927 chore(main): release 0.22.4 (#765) 2025-12-02 17:11:48 -05:00
Igor Monadical
bd5df1ce2e fix: Multitrack mixdown optimisation 2 (#764)
* Revert "fix: Skip mixdown for multitrack (#760)"

This reverts commit b51b7aa917.

* multitrack mixdown optimisation

* return the "good" ui part of "skip mixdown"

---------

Co-authored-by: Igor Loskutov <igor.loskutoff@gmail.com>
2025-12-02 17:10:06 -05:00
c8024484b3 chore(main): release 0.22.3 (#761) 2025-12-02 09:08:22 +01:00
28f87c09dc fix: align daily room settings (#759)
* Switch platform ui

* Update room settings based on platform

* Add local and none recording options to daily

* Don't create tokens for unauthentikated users

* Enable knocking for private rooms

* Create new meeting on room settings change

* Always use 2-200 option for daily

* Show recording start trigger for daily

* Fix broken test
2025-12-02 09:06:36 +01:00
8 changed files with 216 additions and 97 deletions

View File

@@ -1,5 +1,19 @@
# Changelog # Changelog
## [0.22.4](https://github.com/Monadical-SAS/reflector/compare/v0.22.3...v0.22.4) (2025-12-02)
### Bug Fixes
* Multitrack mixdown optimisation 2 ([#764](https://github.com/Monadical-SAS/reflector/issues/764)) ([bd5df1c](https://github.com/Monadical-SAS/reflector/commit/bd5df1ce2ebf35d7f3413b295e56937a9a28ef7b))
## [0.22.3](https://github.com/Monadical-SAS/reflector/compare/v0.22.2...v0.22.3) (2025-12-02)
### Bug Fixes
* align daily room settings ([#759](https://github.com/Monadical-SAS/reflector/issues/759)) ([28f87c0](https://github.com/Monadical-SAS/reflector/commit/28f87c09dc459846873d0dde65b03e3d7b2b9399))
## [0.22.2](https://github.com/Monadical-SAS/reflector/compare/v0.22.1...v0.22.2) (2025-12-02) ## [0.22.2](https://github.com/Monadical-SAS/reflector/compare/v0.22.1...v0.22.2) (2025-12-02)

View File

@@ -40,6 +40,10 @@ class RoomProperties(BaseModel):
) )
enable_chat: bool = Field(default=True, description="Enable in-meeting chat") enable_chat: bool = Field(default=True, description="Enable in-meeting chat")
enable_screenshare: bool = Field(default=True, description="Enable screen sharing") enable_screenshare: bool = Field(default=True, description="Enable screen sharing")
enable_knocking: bool = Field(
default=False,
description="Enable knocking for private rooms (allows participants to request access)",
)
start_video_off: bool = Field( start_video_off: bool = Field(
default=False, description="Start with video off for all participants" default=False, description="Start with video off for all participants"
) )

View File

@@ -31,7 +31,6 @@ from reflector.processors import AudioFileWriterProcessor
from reflector.processors.audio_waveform_processor import AudioWaveformProcessor from reflector.processors.audio_waveform_processor import AudioWaveformProcessor
from reflector.processors.types import TitleSummary from reflector.processors.types import TitleSummary
from reflector.processors.types import Transcript as TranscriptType from reflector.processors.types import Transcript as TranscriptType
from reflector.settings import settings
from reflector.storage import Storage, get_transcripts_storage from reflector.storage import Storage, get_transcripts_storage
from reflector.utils.daily import ( from reflector.utils.daily import (
filter_cam_audio_tracks, filter_cam_audio_tracks,
@@ -423,7 +422,15 @@ class PipelineMainMultitrack(PipelineMainBase):
# Open all containers with cleanup guaranteed # Open all containers with cleanup guaranteed
for i, url in enumerate(valid_track_urls): for i, url in enumerate(valid_track_urls):
try: try:
c = av.open(url) c = av.open(
url,
options={
# it's trying to stream from s3 by default
"reconnect": "1",
"reconnect_streamed": "1",
"reconnect_delay_max": "5",
},
)
containers.append(c) containers.append(c)
except Exception as e: except Exception as e:
self.logger.warning( self.logger.warning(
@@ -452,6 +459,8 @@ class PipelineMainMultitrack(PipelineMainBase):
frame = next(dec) frame = next(dec)
except StopIteration: except StopIteration:
active[i] = False active[i] = False
# causes stream to move on / unclogs memory
inputs[i].push(None)
continue continue
if frame.sample_rate != target_sample_rate: if frame.sample_rate != target_sample_rate:
@@ -471,8 +480,6 @@ class PipelineMainMultitrack(PipelineMainBase):
mixed.time_base = Fraction(1, target_sample_rate) mixed.time_base = Fraction(1, target_sample_rate)
await writer.push(mixed) await writer.push(mixed)
for in_ctx in inputs:
in_ctx.push(None)
while True: while True:
try: try:
mixed = sink.pull() mixed = sink.pull()
@@ -632,55 +639,43 @@ class PipelineMainMultitrack(PipelineMainBase):
transcript.data_path.mkdir(parents=True, exist_ok=True) transcript.data_path.mkdir(parents=True, exist_ok=True)
if settings.SKIP_MIXDOWN: mp3_writer = AudioFileWriterProcessor(
self.logger.warning( path=str(transcript.audio_mp3_filename),
"SKIP_MIXDOWN enabled: Skipping mixdown and waveform generation. " on_duration=self.on_duration,
"UI will have no audio playback or waveform.", )
num_tracks=len(padded_track_urls), await self.mixdown_tracks(padded_track_urls, mp3_writer, offsets_seconds=None)
transcript_id=transcript.id, await mp3_writer.flush()
)
else:
mp3_writer = AudioFileWriterProcessor(
path=str(transcript.audio_mp3_filename),
on_duration=self.on_duration,
)
await self.mixdown_tracks(
padded_track_urls, mp3_writer, offsets_seconds=None
)
await mp3_writer.flush()
if not transcript.audio_mp3_filename.exists(): if not transcript.audio_mp3_filename.exists():
raise Exception( raise Exception(
"Mixdown failed - no MP3 file generated. Cannot proceed without playable audio." "Mixdown failed - no MP3 file generated. Cannot proceed without playable audio."
)
storage_path = f"{transcript.id}/audio.mp3"
# Use file handle streaming to avoid loading entire MP3 into memory
mp3_size = transcript.audio_mp3_filename.stat().st_size
with open(transcript.audio_mp3_filename, "rb") as mp3_file:
await transcript_storage.put_file(storage_path, mp3_file)
mp3_url = await transcript_storage.get_file_url(storage_path)
await transcripts_controller.update(
transcript, {"audio_location": "storage"}
) )
self.logger.info( storage_path = f"{transcript.id}/audio.mp3"
f"Uploaded mixed audio to storage", # Use file handle streaming to avoid loading entire MP3 into memory
storage_path=storage_path, mp3_size = transcript.audio_mp3_filename.stat().st_size
size=mp3_size, with open(transcript.audio_mp3_filename, "rb") as mp3_file:
url=mp3_url, await transcript_storage.put_file(storage_path, mp3_file)
) mp3_url = await transcript_storage.get_file_url(storage_path)
self.logger.info("Generating waveform from mixed audio") await transcripts_controller.update(transcript, {"audio_location": "storage"})
waveform_processor = AudioWaveformProcessor(
audio_path=transcript.audio_mp3_filename, self.logger.info(
waveform_path=transcript.audio_waveform_filename, f"Uploaded mixed audio to storage",
on_waveform=self.on_waveform, storage_path=storage_path,
) size=mp3_size,
waveform_processor.set_pipeline(self.empty_pipeline) url=mp3_url,
await waveform_processor.flush() )
self.logger.info("Waveform generated successfully")
self.logger.info("Generating waveform from mixed audio")
waveform_processor = AudioWaveformProcessor(
audio_path=transcript.audio_mp3_filename,
waveform_path=transcript.audio_waveform_filename,
on_waveform=self.on_waveform,
)
waveform_processor.set_pipeline(self.empty_pipeline)
await waveform_processor.flush()
self.logger.info("Waveform generated successfully")
speaker_transcripts: list[TranscriptType] = [] speaker_transcripts: list[TranscriptType] = []
for idx, padded_url in enumerate(padded_track_urls): for idx, padded_url in enumerate(padded_track_urls):

View File

@@ -138,14 +138,6 @@ class Settings(BaseSettings):
DAILY_WEBHOOK_UUID: str | None = ( DAILY_WEBHOOK_UUID: str | None = (
None # Webhook UUID for this environment. Not used by production code None # Webhook UUID for this environment. Not used by production code
) )
# Multitrack processing
# SKIP_MIXDOWN: When True, skips audio mixdown and waveform generation.
# Transcription still works using individual tracks. Useful for:
# - Diagnosing OOM issues in mixdown
# - Fast processing when audio playback is not needed
# Note: UI will have no audio playback or waveform when enabled.
SKIP_MIXDOWN: bool = True
# Platform Configuration # Platform Configuration
DEFAULT_VIDEO_PLATFORM: Platform = WHEREBY_PLATFORM DEFAULT_VIDEO_PLATFORM: Platform = WHEREBY_PLATFORM

View File

@@ -31,6 +31,7 @@ class DailyClient(VideoPlatformClient):
PLATFORM_NAME: Platform = "daily" PLATFORM_NAME: Platform = "daily"
TIMESTAMP_FORMAT = "%Y%m%d%H%M%S" TIMESTAMP_FORMAT = "%Y%m%d%H%M%S"
RECORDING_NONE: RecordingType = "none" RECORDING_NONE: RecordingType = "none"
RECORDING_LOCAL: RecordingType = "local"
RECORDING_CLOUD: RecordingType = "cloud" RECORDING_CLOUD: RecordingType = "cloud"
def __init__(self, config: VideoPlatformConfig): def __init__(self, config: VideoPlatformConfig):
@@ -54,19 +55,23 @@ class DailyClient(VideoPlatformClient):
timestamp = datetime.now().strftime(self.TIMESTAMP_FORMAT) timestamp = datetime.now().strftime(self.TIMESTAMP_FORMAT)
room_name = f"{room_name_prefix}{ROOM_PREFIX_SEPARATOR}{timestamp}" room_name = f"{room_name_prefix}{ROOM_PREFIX_SEPARATOR}{timestamp}"
enable_recording = None
if room.recording_type == self.RECORDING_LOCAL:
enable_recording = "local"
elif room.recording_type == self.RECORDING_CLOUD:
enable_recording = "raw-tracks"
properties = RoomProperties( properties = RoomProperties(
enable_recording="raw-tracks" enable_recording=enable_recording,
if room.recording_type != self.RECORDING_NONE
else False,
enable_chat=True, enable_chat=True,
enable_screenshare=True, enable_screenshare=True,
enable_knocking=room.is_locked,
start_video_off=False, start_video_off=False,
start_audio_off=False, start_audio_off=False,
exp=int(end_date.timestamp()), exp=int(end_date.timestamp()),
) )
# Only configure recordings_bucket if recording is enabled if room.recording_type == self.RECORDING_CLOUD:
if room.recording_type != self.RECORDING_NONE:
daily_storage = get_dailyco_storage() daily_storage = get_dailyco_storage()
assert daily_storage.bucket_name, "S3 bucket must be configured" assert daily_storage.bucket_name, "S3 bucket must be configured"
properties.recordings_bucket = RecordingsBucketConfig( properties.recordings_bucket = RecordingsBucketConfig(
@@ -172,15 +177,16 @@ class DailyClient(VideoPlatformClient):
async def create_meeting_token( async def create_meeting_token(
self, self,
room_name: DailyRoomName, room_name: DailyRoomName,
enable_recording: bool, start_cloud_recording: bool,
enable_recording_ui: bool,
user_id: NonEmptyString | None = None, user_id: NonEmptyString | None = None,
is_owner: bool = False, is_owner: bool = False,
) -> NonEmptyString: ) -> NonEmptyString:
properties = MeetingTokenProperties( properties = MeetingTokenProperties(
room_name=room_name, room_name=room_name,
user_id=user_id, user_id=user_id,
start_cloud_recording=enable_recording, start_cloud_recording=start_cloud_recording,
enable_recording_ui=False, enable_recording_ui=enable_recording_ui,
is_owner=is_owner, is_owner=is_owner,
) )
request = CreateMeetingTokenRequest(properties=properties) request = CreateMeetingTokenRequest(properties=properties)

View File

@@ -89,7 +89,7 @@ class CreateRoom(BaseModel):
ics_url: Optional[str] = None ics_url: Optional[str] = None
ics_fetch_interval: int = 300 ics_fetch_interval: int = 300
ics_enabled: bool = False ics_enabled: bool = False
platform: Optional[Platform] = None platform: Platform
class UpdateRoom(BaseModel): class UpdateRoom(BaseModel):
@@ -248,7 +248,7 @@ async def rooms_create(
ics_url=room.ics_url, ics_url=room.ics_url,
ics_fetch_interval=room.ics_fetch_interval, ics_fetch_interval=room.ics_fetch_interval,
ics_enabled=room.ics_enabled, ics_enabled=room.ics_enabled,
platform=room.platform or settings.DEFAULT_VIDEO_PLATFORM, platform=room.platform,
) )
@@ -310,6 +310,22 @@ async def rooms_create_meeting(
room=room, current_time=current_time room=room, current_time=current_time
) )
if meeting is not None:
settings_match = (
meeting.is_locked == room.is_locked
and meeting.room_mode == room.room_mode
and meeting.recording_type == room.recording_type
and meeting.recording_trigger == room.recording_trigger
and meeting.platform == room.platform
)
if not settings_match:
logger.info(
f"Room settings changed for {room_name}, creating new meeting",
room_id=room.id,
old_meeting_id=meeting.id,
)
meeting = None
if meeting is None: if meeting is None:
end_date = current_time + timedelta(hours=8) end_date = current_time + timedelta(hours=8)
@@ -549,21 +565,16 @@ async def rooms_join_meeting(
if meeting.end_date <= current_time: if meeting.end_date <= current_time:
raise HTTPException(status_code=400, detail="Meeting has ended") raise HTTPException(status_code=400, detail="Meeting has ended")
if meeting.platform == "daily": if meeting.platform == "daily" and user_id is not None:
client = create_platform_client(meeting.platform) client = create_platform_client(meeting.platform)
enable_recording = room.recording_trigger != "none"
token = await client.create_meeting_token( token = await client.create_meeting_token(
meeting.room_name, meeting.room_name,
enable_recording=enable_recording, start_cloud_recording=meeting.recording_type == "cloud",
enable_recording_ui=meeting.recording_type == "local",
user_id=user_id, user_id=user_id,
is_owner=user_id == room.user_id, is_owner=user_id == room.user_id,
) )
meeting = meeting.model_copy() meeting = meeting.model_copy()
meeting.room_url = add_query_param(meeting.room_url, "t", token) meeting.room_url = add_query_param(meeting.room_url, "t", token)
if meeting.host_room_url:
meeting.host_room_url = add_query_param(meeting.host_room_url, "t", token)
if user_id != room.user_id and meeting.platform == "whereby":
meeting.host_room_url = ""
return meeting return meeting

View File

@@ -67,6 +67,11 @@ const recordingTypeOptions: SelectOption[] = [
{ label: "Cloud", value: "cloud" }, { label: "Cloud", value: "cloud" },
]; ];
const platformOptions: SelectOption[] = [
{ label: "Whereby", value: "whereby" },
{ label: "Daily", value: "daily" },
];
const roomInitialState = { const roomInitialState = {
name: "", name: "",
zulipAutoPost: false, zulipAutoPost: false,
@@ -82,6 +87,7 @@ const roomInitialState = {
icsUrl: "", icsUrl: "",
icsEnabled: false, icsEnabled: false,
icsFetchInterval: 5, icsFetchInterval: 5,
platform: "whereby",
}; };
export default function RoomsList() { export default function RoomsList() {
@@ -99,6 +105,11 @@ export default function RoomsList() {
const recordingTypeCollection = createListCollection({ const recordingTypeCollection = createListCollection({
items: recordingTypeOptions, items: recordingTypeOptions,
}); });
const platformCollection = createListCollection({
items: platformOptions,
});
const [roomInput, setRoomInput] = useState<null | typeof roomInitialState>( const [roomInput, setRoomInput] = useState<null | typeof roomInitialState>(
null, null,
); );
@@ -143,15 +154,24 @@ export default function RoomsList() {
zulipStream: detailedEditedRoom.zulip_stream, zulipStream: detailedEditedRoom.zulip_stream,
zulipTopic: detailedEditedRoom.zulip_topic, zulipTopic: detailedEditedRoom.zulip_topic,
isLocked: detailedEditedRoom.is_locked, isLocked: detailedEditedRoom.is_locked,
roomMode: detailedEditedRoom.room_mode, roomMode:
detailedEditedRoom.platform === "daily"
? "group"
: detailedEditedRoom.room_mode,
recordingType: detailedEditedRoom.recording_type, recordingType: detailedEditedRoom.recording_type,
recordingTrigger: detailedEditedRoom.recording_trigger, recordingTrigger:
detailedEditedRoom.platform === "daily"
? detailedEditedRoom.recording_type === "cloud"
? "automatic-2nd-participant"
: "none"
: detailedEditedRoom.recording_trigger,
isShared: detailedEditedRoom.is_shared, isShared: detailedEditedRoom.is_shared,
webhookUrl: detailedEditedRoom.webhook_url || "", webhookUrl: detailedEditedRoom.webhook_url || "",
webhookSecret: detailedEditedRoom.webhook_secret || "", webhookSecret: detailedEditedRoom.webhook_secret || "",
icsUrl: detailedEditedRoom.ics_url || "", icsUrl: detailedEditedRoom.ics_url || "",
icsEnabled: detailedEditedRoom.ics_enabled || false, icsEnabled: detailedEditedRoom.ics_enabled || false,
icsFetchInterval: detailedEditedRoom.ics_fetch_interval || 5, icsFetchInterval: detailedEditedRoom.ics_fetch_interval || 5,
platform: detailedEditedRoom.platform,
} }
: null, : null,
[detailedEditedRoom], [detailedEditedRoom],
@@ -277,21 +297,32 @@ export default function RoomsList() {
return; return;
} }
const platform: "whereby" | "daily" | null =
room.platform === "whereby" || room.platform === "daily"
? room.platform
: null;
const roomData = { const roomData = {
name: room.name, name: room.name,
zulip_auto_post: room.zulipAutoPost, zulip_auto_post: room.zulipAutoPost,
zulip_stream: room.zulipStream, zulip_stream: room.zulipStream,
zulip_topic: room.zulipTopic, zulip_topic: room.zulipTopic,
is_locked: room.isLocked, is_locked: room.isLocked,
room_mode: room.roomMode, room_mode: platform === "daily" ? "group" : room.roomMode,
recording_type: room.recordingType, recording_type: room.recordingType,
recording_trigger: room.recordingTrigger, recording_trigger:
platform === "daily"
? room.recordingType === "cloud"
? "automatic-2nd-participant"
: "none"
: room.recordingTrigger,
is_shared: room.isShared, is_shared: room.isShared,
webhook_url: room.webhookUrl, webhook_url: room.webhookUrl,
webhook_secret: room.webhookSecret, webhook_secret: room.webhookSecret,
ics_url: room.icsUrl, ics_url: room.icsUrl,
ics_enabled: room.icsEnabled, ics_enabled: room.icsEnabled,
ics_fetch_interval: room.icsFetchInterval, ics_fetch_interval: room.icsFetchInterval,
platform,
}; };
if (isEditing) { if (isEditing) {
@@ -339,15 +370,21 @@ export default function RoomsList() {
zulipStream: roomData.zulip_stream, zulipStream: roomData.zulip_stream,
zulipTopic: roomData.zulip_topic, zulipTopic: roomData.zulip_topic,
isLocked: roomData.is_locked, isLocked: roomData.is_locked,
roomMode: roomData.room_mode, roomMode: roomData.platform === "daily" ? "group" : roomData.room_mode, // Daily always uses 2-200
recordingType: roomData.recording_type, recordingType: roomData.recording_type,
recordingTrigger: roomData.recording_trigger, recordingTrigger:
roomData.platform === "daily"
? roomData.recording_type === "cloud"
? "automatic-2nd-participant"
: "none"
: roomData.recording_trigger,
isShared: roomData.is_shared, isShared: roomData.is_shared,
webhookUrl: roomData.webhook_url || "", webhookUrl: roomData.webhook_url || "",
webhookSecret: roomData.webhook_secret || "", webhookSecret: roomData.webhook_secret || "",
icsUrl: roomData.ics_url || "", icsUrl: roomData.ics_url || "",
icsEnabled: roomData.ics_enabled || false, icsEnabled: roomData.ics_enabled || false,
icsFetchInterval: roomData.ics_fetch_interval || 5, icsFetchInterval: roomData.ics_fetch_interval || 5,
platform: roomData.platform,
}); });
setEditRoomId(roomId); setEditRoomId(roomId);
setIsEditing(true); setIsEditing(true);
@@ -482,6 +519,48 @@ export default function RoomsList() {
)} )}
</Field.Root> </Field.Root>
<Field.Root mt={4}>
<Field.Label>Platform</Field.Label>
<Select.Root
value={[room.platform]}
onValueChange={(e) => {
const newPlatform = e.value[0] as "whereby" | "daily";
const updates: Partial<typeof room> = {
platform: newPlatform,
};
if (newPlatform === "daily") {
updates.roomMode = "group";
updates.recordingTrigger =
room.recordingType === "cloud"
? "automatic-2nd-participant"
: "none";
}
setRoomInput({ ...room, ...updates });
}}
collection={platformCollection}
>
<Select.HiddenSelect />
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select platform" />
</Select.Trigger>
<Select.IndicatorGroup>
<Select.Indicator />
</Select.IndicatorGroup>
</Select.Control>
<Select.Positioner>
<Select.Content>
{platformOptions.map((option) => (
<Select.Item key={option.value} item={option}>
{option.label}
<Select.ItemIndicator />
</Select.Item>
))}
</Select.Content>
</Select.Positioner>
</Select.Root>
</Field.Root>
<Field.Root mt={4}> <Field.Root mt={4}>
<Checkbox.Root <Checkbox.Root
name="isLocked" name="isLocked"
@@ -512,6 +591,7 @@ export default function RoomsList() {
setRoomInput({ ...room, roomMode: e.value[0] }) setRoomInput({ ...room, roomMode: e.value[0] })
} }
collection={roomModeCollection} collection={roomModeCollection}
disabled={room.platform === "daily"}
> >
<Select.HiddenSelect /> <Select.HiddenSelect />
<Select.Control> <Select.Control>
@@ -538,16 +618,26 @@ export default function RoomsList() {
<Field.Label>Recording type</Field.Label> <Field.Label>Recording type</Field.Label>
<Select.Root <Select.Root
value={[room.recordingType]} value={[room.recordingType]}
onValueChange={(e) => onValueChange={(e) => {
setRoomInput({ const newRecordingType = e.value[0];
...room, const updates: Partial<typeof room> = {
recordingType: e.value[0], recordingType: newRecordingType,
recordingTrigger: };
e.value[0] !== "cloud" // For Daily: if cloud, use automatic; otherwise none
if (room.platform === "daily") {
updates.recordingTrigger =
newRecordingType === "cloud"
? "automatic-2nd-participant"
: "none";
} else {
// For Whereby: if not cloud, set to none
updates.recordingTrigger =
newRecordingType !== "cloud"
? "none" ? "none"
: room.recordingTrigger, : room.recordingTrigger;
}) }
} setRoomInput({ ...room, ...updates });
}}
collection={recordingTypeCollection} collection={recordingTypeCollection}
> >
<Select.HiddenSelect /> <Select.HiddenSelect />
@@ -572,7 +662,7 @@ export default function RoomsList() {
</Select.Root> </Select.Root>
</Field.Root> </Field.Root>
<Field.Root mt={4}> <Field.Root mt={4}>
<Field.Label>Cloud recording start trigger</Field.Label> <Field.Label>Recording start trigger</Field.Label>
<Select.Root <Select.Root
value={[room.recordingTrigger]} value={[room.recordingTrigger]}
onValueChange={(e) => onValueChange={(e) =>
@@ -582,7 +672,11 @@ export default function RoomsList() {
}) })
} }
collection={recordingTriggerCollection} collection={recordingTriggerCollection}
disabled={room.recordingType !== "cloud"} disabled={
room.recordingType !== "cloud" ||
(room.platform === "daily" &&
room.recordingType === "cloud")
}
> >
<Select.HiddenSelect /> <Select.HiddenSelect />
<Select.Control> <Select.Control>

View File

@@ -52,7 +52,7 @@ export default function DailyRoom({ meeting }: DailyRoomProps) {
join(); join();
}, [meeting?.id, roomName, authLastUserId]); }, [meeting?.id, roomName, authLastUserId]);
const roomUrl = joinedMeeting?.host_room_url || joinedMeeting?.room_url; const roomUrl = joinedMeeting?.room_url;
const handleLeave = useCallback(() => { const handleLeave = useCallback(() => {
router.push("/browse"); router.push("/browse");
@@ -89,14 +89,17 @@ export default function DailyRoom({ meeting }: DailyRoomProps) {
frame.on("left-meeting", handleLeave); frame.on("left-meeting", handleLeave);
// TODO this method must not ignore no-recording option
// TODO this method is here to make dev environments work in some cases (not examined which)
frame.on("joined-meeting", async () => { frame.on("joined-meeting", async () => {
try { try {
assertExists( const frameInstance = assertExists(
frame, frame,
"frame object got lost somewhere after frame.on was called", "frame object got lost somewhere after frame.on was called",
).startRecording({ type: "raw-tracks" }); );
if (meeting.recording_type === "cloud") {
console.log("Starting cloud recording");
await frameInstance.startRecording({ type: "raw-tracks" });
}
} catch (error) { } catch (error) {
console.error("Failed to start recording:", error); console.error("Failed to start recording:", error);
} }