Closed JamesPHoughton closed 10 months ago
In tests, if the player who started the recording drops out, it does indeed stop the recording and doesn't restart it.
It looks as though we may be able to start a recording using the API, however, which may not be tied to the individual participant?
https://docs.daily.co/reference/rest-api/rooms/recordings/start#example-requests
If we try to start the recording from the onStageStart callback:
Empirica.onStageStart(({ stage }) => {
const discussion = stage?.get("discussion");
const { config } = stage.currentGame.batch.get("config");
const videoStorageLocation = config?.videoStorageLocation;
if (discussion?.chatType === "video" && videoStorageLocation !== "none") {
const dailyRoomName = stage.currentGame.get("dailyRoomName");
startRecording(dailyRoomName);
}
});
Then we get an error, because nobody has actually joined the room yet.
[server] 21:58:47.230 ERR Error occurred while trying to start recording room 20231214_0258_labDem04XSWF [md [AxiosError]: Request failed with status code 404] {
[server] code: 'ERR_BAD_REQUEST',
[server] config: {
[server] transitional: {
[server] silentJSONParsing: true,
[server] forcedJSONParsing: true,
[server] clarifyTimeoutError: false
[server] },
[server] adapter: [Function (anonymous)],
[server] transformRequest: [ [Function (anonymous)] ],
[server] transformResponse: [ [Function (anonymous)] ],
[server] timeout: 0,
[server] xsrfCookieName: 'XSRF-TOKEN',
[server] xsrfHeaderName: 'X-XSRF-TOKEN',
[server] maxContentLength: -1,
[server] maxBodyLength: -1,
[server] env: { FormData: [Function] },
[server] validateStatus: [Function: validateStatus],
[server] headers: {
[server] Accept: 'application/json',
[server] 'Content-Type': 'application/json',
[server] Authorization: 'Bearer 81f1e20093fa32465458e5ca9573695c04a99ee81f0a571057338d4b1cc54896',
[server] 'User-Agent': 'axios/0.27.2',
[server] 'Content-Length': 21
[server] },
[server] method: 'post',
[server] url: 'https://api.daily.co/v1/rooms/20231214_0258_labDem04XSWF/recordings/start',
[server] data: '{"type":"raw-tracks"}'
[server] },
[server] request: <ref *1> ClientRequest {
[server] _events: [Object: null prototype] {
[server] abort: [Function (anonymous)],
[server] aborted: [Function (anonymous)],
[server] connect: [Function (anonymous)],
[server] error: [Function (anonymous)],
[server] socket: [Function (anonymous)],
[server] timeout: [Function (anonymous)],
[server] prefinish: [Function: requestOnPrefinish]
[server] },
[server] _eventsCount: 7,
[server] _maxListeners: undefined,
[server] outputData: [],
[server] outputSize: 0,
[server] writable: true,
[server] destroyed: false,
[server] _last: true,
[server] chunkedEncoding: false,
[server] shouldKeepAlive: false,
[server] maxRequestsOnConnectionReached: false,
[server] _defaultKeepAlive: true,
[server] useChunkedEncodingByDefault: true,
[server] sendDate: false,
[server] _removedConnection: false,
[server] _removedContLen: false,
[server] _removedTE: false,
[server] _contentLength: null,
[server] _hasBody: true,
[server] _trailer: '',
[server] finished: true,
[server] _headerSent: true,
[server] _closed: false,
[server] socket: TLSSocket {
[server] _tlsOptions: [Object],
[server] _secureEstablished: true,
[server] _securePending: false,
[server] _newSessionPending: false,
[server] _controlReleased: true,
[server] secureConnecting: false,
[server] _SNICallback: null,
[server] servername: 'api.daily.co',
[server] alpnProtocol: false,
[server] authorized: true,
[server] authorizationError: null,
[server] encrypted: true,
[server] _events: [Object: null prototype],
[server] _eventsCount: 10,
[server] connecting: false,
[server] _hadError: false,
[server] _parent: null,
[server] _host: 'api.daily.co',
[server] _readableState: [ReadableState],
[server] _maxListeners: undefined,
[server] _writableState: [WritableState],
[server] allowHalfOpen: false,
[server] _sockname: null,
[server] _pendingData: null,
[server] _pendingEncoding: '',
[server] server: undefined,
[server] _server: null,
[server] ssl: [TLSWrap],
[server] _requestCert: true,
[server] _rejectUnauthorized: true,
[server] parser: null,
[server] _httpMessage: [Circular *1],
[server] [Symbol(res)]: [TLSWrap],
[server] [Symbol(verified)]: true,
[server] [Symbol(pendingSession)]: null,
[server] [Symbol(async_id_symbol)]: 3386,
[server] [Symbol(kHandle)]: [TLSWrap],
[server] [Symbol(lastWriteQueueSize)]: 0,
[server] [Symbol(timeout)]: null,
[server] [Symbol(kBuffer)]: null,
[server] [Symbol(kBufferCb)]: null,
[server] [Symbol(kBufferGen)]: null,
[server] [Symbol(kCapture)]: false,
[server] [Symbol(kSetNoDelay)]: false,
[server] [Symbol(kSetKeepAlive)]: true,
[server] [Symbol(kSetKeepAliveInitialDelay)]: 60,
[server] [Symbol(kBytesRead)]: 0,
[server] [Symbol(kBytesWritten)]: 0,
[server] [Symbol(connect-options)]: [Object],
[server] [Symbol(RequestTimeout)]: undefined
[server] },
[server] _header: 'POST /v1/rooms/20231214_0258_labDem04XSWF/recordings/start HTTP/1.1\r\n' +
[server] 'Accept: application/json\r\n' +
[server] 'Content-Type: application/json\r\n' +
[server] 'Authorization: Bearer 81f1e20093fa32465458e5ca9573695c04a99ee81f0a571057338d4b1cc54896\r\n' +
[server] 'User-Agent: axios/0.27.2\r\n' +
[server] 'Content-Length: 21\r\n' +
[server] 'Host: api.daily.co\r\n' +
[server] 'Connection: close\r\n' +
[server] '\r\n',
[server] _keepAliveTimeout: 0,
[server] _onPendingData: [Function: nop],
[server] agent: Agent {
[server] _events: [Object: null prototype],
[server] _eventsCount: 2,
[server] _maxListeners: undefined,
[server] defaultPort: 443,
[server] protocol: 'https:',
[server] options: [Object: null prototype],
[server] requests: [Object: null prototype] {},
[server] sockets: [Object: null prototype],
[server] freeSockets: [Object: null prototype] {},
[server] keepAliveMsecs: 1000,
[server] keepAlive: false,
[server] maxSockets: Infinity,
[server] maxFreeSockets: 256,
[server] scheduling: 'lifo',
[server] maxTotalSockets: Infinity,
[server] totalSocketCount: 1,
[server] maxCachedSessions: 100,
[server] _sessionCache: [Object],
[server] [Symbol(kCapture)]: false
[server] },
[server] socketPath: undefined,
[server] method: 'POST',
[server] maxHeaderSize: undefined,
[server] insecureHTTPParser: undefined,
[server] path: '/v1/rooms/20231214_0258_labDem04XSWF/recordings/start',
[server] _ended: true,
[server] res: IncomingMessage {
[server] _readableState: [ReadableState],
[server] _events: [Object: null prototype],
[server] _eventsCount: 4,
[server] _maxListeners: undefined,
[server] socket: [TLSSocket],
[server] httpVersionMajor: 1,
[server] httpVersionMinor: 1,
[server] httpVersion: '1.1',
[server] complete: true,
[server] rawHeaders: [Array],
[server] rawTrailers: [],
[server] aborted: false,
[server] upgrade: false,
[server] url: '',
[server] method: null,
[server] statusCode: 404,
[server] statusMessage: 'Not Found',
[server] client: [TLSSocket],
[server] _consuming: false,
[server] _dumped: false,
[server] req: [Circular *1],
[server] responseUrl: 'https://api.daily.co/v1/rooms/20231214_0258_labDem04XSWF/recordings/start',
[server] redirects: [],
[server] [Symbol(kCapture)]: false,
[server] [Symbol(kHeaders)]: [Object],
[server] [Symbol(kHeadersCount)]: 18,
[server] [Symbol(kTrailers)]: null,
[server] [Symbol(kTrailersCount)]: 0,
[server] [Symbol(RequestTimeout)]: undefined
[server] },
[server] aborted: false,
[server] timeoutCb: null,
[server] upgradeOrConnect: false,
[server] parser: null,
[server] maxHeadersCount: null,
[server] reusedSocket: false,
[server] host: 'api.daily.co',
[server] protocol: 'https:',
[server] _redirectable: Writable {
[server] _writableState: [WritableState],
[server] _events: [Object: null prototype],
[server] _eventsCount: 3,
[server] _maxListeners: undefined,
[server] _options: [Object],
[server] _ended: true,
[server] _ending: true,
[server] _redirectCount: 0,
[server] _redirects: [],
[server] _requestBodyLength: 21,
[server] _requestBodyBuffers: [],
[server] _onNativeResponse: [Function (anonymous)],
[server] _currentRequest: [Circular *1],
[server] _currentUrl: 'https://api.daily.co/v1/rooms/20231214_0258_labDem04XSWF/recordings/start',
[server] [Symbol(kCapture)]: false
[server] },
[server] [Symbol(kCapture)]: false,
[server] [Symbol(kNeedDrain)]: false,
[server] [Symbol(corked)]: 0,
[server] [Symbol(kOutHeaders)]: [Object: null prototype] {
[server] accept: [Array],
[server] 'content-type': [Array],
[server] authorization: [Array],
[server] 'user-agent': [Array],
[server] 'content-length': [Array],
[server] host: [Array]
[server] },
[server] [Symbol(kUniqueHeaders)]: null
[server] },
[server] response: {
[server] status: 404,
[server] statusText: 'Not Found',
[server] headers: {
[server] date: 'Thu, 14 Dec 2023 02:58:47 GMT',
[server] 'content-type': 'application/json; charset=utf-8',
[server] 'content-length': '107',
[server] connection: 'close',
[server] 'strict-transport-security': 'max-age=31536000 ; includeSubDomains',
[server] 'access-control-allow-origin': '*',
[server] 'access-control-allow-headers': 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Invite-Token, X-DailySessionId, X-DailyJoinToken, X-DailyAboutClient',
[server] 'access-control-allow-methods': 'OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE',
[server] etag: 'W/"6b-0Pyq3gBm5flcZ2EBstaj0JqkOT4"'
[server] },
[server] config: {
[server] transitional: [Object],
[server] adapter: [Function (anonymous)],
[server] transformRequest: [Array],
[server] transformResponse: [Array],
[server] timeout: 0,
[server] xsrfCookieName: 'XSRF-TOKEN',
[server] xsrfHeaderName: 'X-XSRF-TOKEN',
[server] maxContentLength: -1,
[server] maxBodyLength: -1,
[server] env: [Object],
[server] validateStatus: [Function: validateStatus],
[server] headers: [Object],
[server] method: 'post',
[server] url: 'https://api.daily.co/v1/rooms/20231214_0258_labDem04XSWF/recordings/start',
[server] data: '{"type":"raw-tracks"}'
[server] },
[server] request: <ref *1> ClientRequest {
[server] _events: [Object: null prototype],
[server] _eventsCount: 7,
[server] _maxListeners: undefined,
[server] outputData: [],
[server] outputSize: 0,
[server] writable: true,
[server] destroyed: false,
[server] _last: true,
[server] chunkedEncoding: false,
[server] shouldKeepAlive: false,
[server] maxRequestsOnConnectionReached: false,
[server] _defaultKeepAlive: true,
[server] useChunkedEncodingByDefault: true,
[server] sendDate: false,
[server] _removedConnection: false,
[server] _removedContLen: false,
[server] _removedTE: false,
[server] _contentLength: null,
[server] _hasBody: true,
[server] _trailer: '',
[server] finished: true,
[server] _headerSent: true,
[server] _closed: false,
[server] socket: [TLSSocket],
[server] _header: 'POST /v1/rooms/20231214_0258_labDem04XSWF/recordings/start HTTP/1.1\r\n' +
[server] 'Accept: application/json\r\n' +
[server] 'Content-Type: application/json\r\n' +
[server] 'Authorization: Bearer 81f1e20093fa32465458e5ca9573695c04a99ee81f0a571057338d4b1cc54896\r\n' +
[server] 'User-Agent: axios/0.27.2\r\n' +
[server] 'Content-Length: 21\r\n' +
[server] 'Host: api.daily.co\r\n' +
[server] 'Connection: close\r\n' +
[server] '\r\n',
[server] _keepAliveTimeout: 0,
[server] _onPendingData: [Function: nop],
[server] agent: [Agent],
[server] socketPath: undefined,
[server] method: 'POST',
[server] maxHeaderSize: undefined,
[server] insecureHTTPParser: undefined,
[server] path: '/v1/rooms/20231214_0258_labDem04XSWF/recordings/start',
[server] _ended: true,
[server] res: [IncomingMessage],
[server] aborted: false,
[server] timeoutCb: null,
[server] upgradeOrConnect: false,
[server] parser: null,
[server] maxHeadersCount: null,
[server] reusedSocket: false,
[server] host: 'api.daily.co',
[server] protocol: 'https:',
[server] _redirectable: [Writable],
[server] [Symbol(kCapture)]: false,
[server] [Symbol(kNeedDrain)]: false,
[server] [Symbol(corked)]: 0,
[server] [Symbol(kOutHeaders)]: [Object: null prototype],
[server] [Symbol(kUniqueHeaders)]: null
[server] },
[server] data: {
[server] error: 'not-found',
[server] info: 'room 20231214_0258_labDem04XSWF does not seem to be hosting a call currently'
[server] }
[server] }
[server] }
We could still use this, and set a 3-second retry for up to 10 retries?
Alternately, when anyone successfully joins the room, have them set a flag on the stage object to say so, and have a callback watching that:
// client/src/components/VideoCall.jsx
...
callFrame.on("joined-meeting", (event) => {
stage.set("callStarted", true);
});
...
// server/src/callbacks.js
Empirica.on("stage", "callStarted", async (ctx, { stage, callStarted }) => {
if (!callStarted) return;
const discussion = stage?.get("discussion");
const { config } = stage.currentGame.batch.get("config");
const videoStorageLocation = config?.videoStorageLocation;
if (discussion?.chatType === "video" && videoStorageLocation !== "none") {
const dailyRoomName = stage.currentGame.get("dailyRoomName");
startRecording(dailyRoomName);
}
});
We may still have the timing issue, and may have to set a 3-second fallback anyways. Unfortunately, this strategy is currently running afoul of https://github.com/empiricaly/empirica/issues/453
When any player joins a call, and we get the "joined-meeting" callback from daily, stage.set("callStarted", true)
. Then look for this value to be set in a callback, and try to start the recording. Give 10 retries with a 3 second delay.
According to the docs, the recording is started by one of the participants:
https://docs.daily.co/guides/products/live-streaming-recording/recording-calls-with-the-daily-api#tips-for-the-best-recording-experience
What happens if that person drops out, or reconnects?