Skip to content

Commit

Permalink
Embedding videos using Opencast IDs (#1174)
Browse files Browse the repository at this point in the history
Previously, embedding videos was only possible using the Tobira ID. This
change introduces the ability to embed videos with Opencast IDs using
the same embedding format (`/~embed/!v/:<opencast-id>`). This
enhancement improves flexibility and consistency in video embedding
options.
  • Loading branch information
LukasKalbertodt authored Jun 5, 2024
2 parents 1563c80 + 06881b4 commit 3ea80fc
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 54 deletions.
3 changes: 2 additions & 1 deletion frontend/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ManageVideosRoute } from "./routes/manage/Video";
import { UploadRoute } from "./routes/Upload";
import { SearchRoute } from "./routes/Search";
import { InvalidUrlRoute } from "./routes/InvalidUrl";
import { BlockEmbedRoute, EmbedVideoRoute } from "./routes/Embed";
import { BlockEmbedRoute, EmbedOpencastVideoRoute, EmbedVideoRoute } from "./routes/Embed";
import { ManageVideoDetailsRoute } from "./routes/manage/Video/Details";
import { ManageVideoTechnicalDetailsRoute } from "./routes/manage/Video/TechnicalDetails";
import React from "react";
Expand Down Expand Up @@ -52,6 +52,7 @@ const {
AddChildRoute,
ManageRealmContentRoute,
EmbedVideoRoute,
EmbedOpencastVideoRoute,
],
});

Expand Down
146 changes: 93 additions & 53 deletions frontend/src/routes/Embed.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,24 @@
import { ReactNode, Suspense } from "react";
import { LuFrown, LuAlertTriangle } from "react-icons/lu";
import { Translation, useTranslation } from "react-i18next";
import { graphql, PreloadedQuery, usePreloadedQuery } from "react-relay";
import {
graphql, GraphQLTaggedNode, PreloadedQuery, useFragment, usePreloadedQuery,
} from "react-relay";
import { unreachable } from "@opencast/appkit";

import { eventId, isSynced, keyOfId } from "../util";
import { GlobalErrorBoundary } from "../util/err";
import { loadQuery } from "../relay";
import { makeRoute } from "../rauta";
import { makeRoute, MatchedRoute } from "../rauta";
import { Player, PlayerPlaceholder } from "../ui/player";
import { Spinner } from "../ui/Spinner";
import { MovingTruck } from "../ui/Waiting";
import { b64regex } from "./util";
import { EmbedQuery } from "./__generated__/EmbedQuery.graphql";
import { EmbedDirectOpencastQuery } from "./__generated__/EmbedDirectOpencastQuery.graphql";
import { EmbedEventData$key } from "./__generated__/EmbedEventData.graphql";
import { PlayerContextProvider } from "../ui/player/PlayerContext";


const query = graphql`
query EmbedQuery($id: ID!) {
eventById(id: $id) {
__typename
... on NotAllowed { dummy }
... on AuthorizedEvent {
title
created
isLive
opencastId
creators
metadata
description
series { title opencastId }
syncedData {
updated
startTime
endTime
duration
thumbnail
tracks { uri flavor mimetype resolution isMaster }
captions { uri lang }
segments { uri startTime }
}
}
}
}
`;

export const EmbedVideoRoute = makeRoute({
url: ({ videoId }: { videoId: string }) => `/~embed/!v/${keyOfId(videoId)}`,
match: url => {
Expand All @@ -55,35 +29,101 @@ export const EmbedVideoRoute = makeRoute({
}
const videoId = decodeURIComponent(params[1]);

const query = graphql`
query EmbedQuery($id: ID!) {
event: eventById(id: $id) { ... EmbedEventData }
}
`;

const queryRef = loadQuery<EmbedQuery>(query, { id: eventId(videoId) });

return {
render: () => <ErrorBoundary>
<Suspense fallback={
<PlayerPlaceholder>
<Spinner css={{
"& > circle": {
stroke: "white",
},
}} />
</PlayerPlaceholder>
}>
<PlayerContextProvider>
<Embed queryRef={queryRef} />
</PlayerContextProvider>
</Suspense>
</ErrorBoundary>,
dispose: () => queryRef.dispose(),
};
return matchedEmbedRoute(query, queryRef);
},
});

export const EmbedOpencastVideoRoute = makeRoute({
url: (args: { ocID: string }) => `/~embed/!v/:${args.ocID}`,
match: url => {
const regex = new RegExp("^/~embed/!v/:([^/]+)$", "u");
const matches = regex.exec(url.pathname);
if (!matches) {
return null;
}

const query = graphql`
query EmbedDirectOpencastQuery($id: String!) {
event: eventByOpencastId(id: $id) { ... EmbedEventData }
}
`;

const videoId = decodeURIComponent(matches[1]);
const queryRef = loadQuery<EmbedDirectOpencastQuery>(query, { id: videoId });

return matchedEmbedRoute(query, queryRef);
},
});

const matchedEmbedRoute = (
query: GraphQLTaggedNode,
queryRef: PreloadedQuery<EmbedQuery | EmbedDirectOpencastQuery>,
): MatchedRoute => ({
render: () => <ErrorBoundary>
<Suspense fallback={
<PlayerPlaceholder>
<Spinner css={{
"& > circle": {
stroke: "white",
},
}} />
</PlayerPlaceholder>
}>
<PlayerContextProvider>
<Embed query={query} queryRef={queryRef} />
</PlayerContextProvider>
</Suspense>
</ErrorBoundary>,
dispose: () => queryRef.dispose(),
});

const embedEventFragment = graphql`
fragment EmbedEventData on Event {
__typename
... on NotAllowed { dummy }
... on AuthorizedEvent {
title
created
isLive
opencastId
creators
metadata
description
series { title opencastId }
syncedData {
updated
startTime
endTime
duration
thumbnail
tracks { uri flavor mimetype resolution isMaster }
captions { uri lang }
segments { uri startTime }
}
}
}
`;


type EmbedProps = {
queryRef: PreloadedQuery<EmbedQuery>;
query: GraphQLTaggedNode;
queryRef: PreloadedQuery<EmbedQuery|EmbedDirectOpencastQuery>;
};

const Embed: React.FC<EmbedProps> = ({ queryRef }) => {
const { eventById: event } = usePreloadedQuery(query, queryRef);
const Embed: React.FC<EmbedProps> = ({ query, queryRef }) => {
const fragmentRef = usePreloadedQuery(query, queryRef);
const event = useFragment<EmbedEventData$key>(
embedEventFragment,
fragmentRef.event,
);
const { t } = useTranslation();

if (!event) {
Expand Down

0 comments on commit 3ea80fc

Please sign in to comment.