-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* clip: define clip api * changes to the clip task spawn * change source location & check if stream is recording & fix session query * added session id * return recordings * fix recordings url * clip: secondary os * fix * fix * fix * clip: set objectstore id for output clips * clip: use default object store for output asset * addressed comments * addressed comments * api: Create separate session table * clip: added source object store id to task context * rebase * clip: clean output asset json --------- Co-authored-by: Victor Elias <[email protected]>
- Loading branch information
1 parent
0f6a125
commit 2d05e86
Showing
14 changed files
with
419 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
import { validatePost } from "../middleware"; | ||
import { Request, Router } from "express"; | ||
import _ from "lodash"; | ||
import { db } from "../store"; | ||
import { NotFoundError } from "../store/errors"; | ||
import { pathJoin } from "../controllers/helpers"; | ||
import { | ||
createAsset, | ||
validateAssetPayload, | ||
defaultObjectStoreId, | ||
catalystPipelineStrategy, | ||
} from "./asset"; | ||
import { generateUniquePlaybackId } from "./generate-keys"; | ||
import { v4 as uuid } from "uuid"; | ||
import { DBSession } from "../store/session-table"; | ||
import { fetchWithTimeout } from "../util"; | ||
import { DBStream } from "../store/stream-table"; | ||
import { toExternalAsset } from "./asset"; | ||
import { toStringValues } from "./helpers"; | ||
import mung from "express-mung"; | ||
import { Asset } from "../schema/types"; | ||
import { WithID } from "../store/types"; | ||
|
||
const app = Router(); | ||
|
||
app.use( | ||
mung.jsonAsync(async function cleanWriteOnlyResponses( | ||
data: WithID<Asset>[] | WithID<Asset> | { asset: WithID<Asset> }, | ||
req | ||
) { | ||
const { details } = toStringValues(req.query); | ||
const toExternalAssetFunc = (a: Asset) => | ||
toExternalAsset(a, req.config, !!details, req.user.admin); | ||
|
||
if (Array.isArray(data)) { | ||
return Promise.all(data.map(toExternalAssetFunc)); | ||
} | ||
if ("id" in data) { | ||
return toExternalAssetFunc(data); | ||
} | ||
if ("asset" in data) { | ||
return { | ||
...data, | ||
asset: await toExternalAssetFunc(data.asset), | ||
}; | ||
} | ||
return data; | ||
}) | ||
); | ||
|
||
app.post("/", validatePost("clip-payload"), async (req, res) => { | ||
const playbackId = req.body.playbackId; | ||
const userId = req.user.id; | ||
|
||
const id = uuid(); | ||
let uPlaybackId = await generateUniquePlaybackId(id); | ||
|
||
const content = await db.stream.getByPlaybackId(playbackId); //|| | ||
//(await db.asset.getByPlaybackId(playbackId)); | ||
|
||
let isStream: boolean; | ||
if (content && "streamKey" in content) { | ||
isStream = true; | ||
} | ||
|
||
if (!content) { | ||
throw new NotFoundError("Content not found"); | ||
} | ||
|
||
const user = await db.user.get(content.userId); | ||
|
||
if (!user || userId !== content.userId) { | ||
throw new NotFoundError("Content not found"); | ||
} | ||
|
||
if ("suspended" in content && content.suspended) { | ||
throw new NotFoundError("Content not found"); | ||
} | ||
|
||
let url: string; | ||
let session: DBSession; | ||
let objectStoreId: string; | ||
|
||
if (isStream) { | ||
if (!content.record) { | ||
res.status(400).json({ | ||
errors: ["Recording must be enabled on a live stream to create clips"], | ||
}); | ||
} | ||
({ url, session, objectStoreId } = await getRunningRecording(content, req)); | ||
} else { | ||
res | ||
.status(400) | ||
.json({ errors: ["Clipping for assets is not implemented yet"] }); | ||
return; | ||
} | ||
|
||
if (!session) { | ||
throw new Error("Recording session not found"); | ||
} | ||
|
||
let asset = await validateAssetPayload( | ||
id, | ||
uPlaybackId, | ||
content.userId, | ||
Date.now(), | ||
await defaultObjectStoreId(req), | ||
req.config, | ||
{ | ||
name: req.body.name || `clip-${uPlaybackId}`, | ||
}, | ||
{ | ||
type: "clip", | ||
...(isStream ? { sessionId: session.id } : { assetId: content.id }), | ||
} | ||
); | ||
|
||
asset = await createAsset(asset, req.queue); | ||
|
||
const task = await req.taskScheduler.createAndScheduleTask( | ||
"clip", | ||
{ | ||
clip: { | ||
clipStrategy: { | ||
playbackId, | ||
startTime: req.body.startTime, | ||
endTime: req.body.endTime, | ||
}, | ||
catalystPipelineStrategy: catalystPipelineStrategy(req), | ||
url, | ||
sessionId: session.id, | ||
inputId: content.id, | ||
sourceObjectStoreId: objectStoreId, | ||
}, | ||
}, | ||
null, | ||
asset, | ||
userId | ||
); | ||
|
||
res.json({ | ||
task: { id: task.id }, | ||
asset, | ||
}); | ||
}); | ||
|
||
async function getRunningRecording(content: DBStream, req: Request) { | ||
let objectStoreId: string; | ||
|
||
const session = await db.session.getLastSession(content.id); | ||
const os = await db.objectStore.get(req.config.recordCatalystObjectStoreId); | ||
|
||
let url = pathJoin( | ||
os.publicUrl, | ||
session.playbackId, | ||
session.id, | ||
"output.m3u8" | ||
); | ||
|
||
let params = { | ||
method: "HEAD", | ||
timeout: 5 * 1000, | ||
}; | ||
let resp = await fetchWithTimeout(url, params); | ||
|
||
if (resp.status != 200) { | ||
const secondaryOs = req.config.secondaryRecordObjectStoreId | ||
? await db.objectStore.get(req.config.secondaryRecordObjectStoreId) | ||
: undefined; | ||
url = pathJoin( | ||
secondaryOs.publicUrl, | ||
session.playbackId, | ||
session.id, | ||
"output.m3u8" | ||
); | ||
/* | ||
TODO: Enable to check if recording is running on the secondary one | ||
resp = await fetchWithTimeout(url, params); | ||
if (resp.status != 200) { | ||
throw new Error("Recording not found"); | ||
}*/ | ||
|
||
objectStoreId = req.config.secondaryRecordObjectStoreId; | ||
} else { | ||
objectStoreId = req.config.recordCatalystObjectStoreId; | ||
} | ||
|
||
return { | ||
url, | ||
session, | ||
objectStoreId, | ||
}; | ||
} | ||
|
||
export default app; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.