-
Notifications
You must be signed in to change notification settings - Fork 26.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
with-mux-video: move to app router and update packages (#62297)
## App Router Moves the `with-mux-video` example to idiomatic App Router, with goodies like * server component data fetching * server actions * layouts * route groups * loading UI ## Mux Dependencies * @mux/mux-node 7 -> 8 * @mux/mux-player-react 1 -> 2 * @mux/upchunk + custom UI -> @mux/mux-uploader ## In other news... * Fleshed out the README * Updated imagery * Moved from styled jsx to tailwind and lightly updated styles ## Contributor Checklist * [x] The "examples guidelines" are followed from our contributing doc https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md * [x] Make sure the linting passes by running `pnpm build && pnpm lint`. See https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md --------- Co-authored-by: Sam Ko <sam@vercel.com>
- Loading branch information
Showing
42 changed files
with
537 additions
and
837 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,13 @@ | ||
"use client"; | ||
|
||
import { type ComponentPropsWithoutRef } from "react"; | ||
|
||
import LibMuxUploader from "@mux/mux-uploader-react"; | ||
|
||
type Props = { | ||
endpoint: ComponentPropsWithoutRef<typeof LibMuxUploader>["endpoint"]; | ||
onSuccess: () => void; | ||
}; | ||
export default function MuxUploader({ endpoint, onSuccess }: Props) { | ||
return <LibMuxUploader endpoint={endpoint} onSuccess={() => onSuccess()} />; | ||
} |
66 changes: 66 additions & 0 deletions
66
examples/with-mux-video/app/(upload)/asset/[assetId]/AssetStatusPoll.tsx
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,66 @@ | ||
"use client"; | ||
|
||
import { useEffect, useState } from "react"; | ||
|
||
import { Status } from "./types"; | ||
import Link from "@/app/_components/Link"; | ||
|
||
const Oops = () => ( | ||
<p> | ||
This is awkward. Let's <Link href="/">refresh</Link> and try again. | ||
</p> | ||
); | ||
|
||
type Props = { | ||
initialStatus: Status; | ||
checkAssetStatus: () => Promise<Status>; | ||
}; | ||
export default function AssetStatusPoll({ | ||
initialStatus, | ||
checkAssetStatus, | ||
}: Props) { | ||
const [{ status, errors }, setStatus] = useState<Status>(() => initialStatus); | ||
|
||
useEffect(() => { | ||
const poll = async () => setStatus(await checkAssetStatus()); | ||
const interval = setInterval(poll, 1000); | ||
return () => clearInterval(interval); | ||
}, [checkAssetStatus]); | ||
|
||
switch (status) { | ||
case "preparing": | ||
return <p className="animate-pulse">Asset is preparing...</p>; | ||
case "errored": | ||
return ( | ||
<div> | ||
<p className="mb-4">Asset encountered an error.</p> | ||
{Array.isArray(errors) && ( | ||
<ul className="mb-4"> | ||
{errors.map((error, key) => ( | ||
<li key={key}>{JSON.stringify(error)}</li> | ||
))} | ||
</ul> | ||
)} | ||
<Oops /> | ||
</div> | ||
); | ||
case "ready": | ||
return ( | ||
<div> | ||
<p className="mb-4"> | ||
Asset is ready. The app really should've redirected you to it by | ||
now. | ||
</p> | ||
<Oops /> | ||
</div> | ||
); | ||
default: | ||
return ( | ||
<div> | ||
<p className="mb-4">Asset is in an unknown state.</p> | ||
<pre className="mb-4">{JSON.stringify({ status, errors })}</pre> | ||
<Oops /> | ||
</div> | ||
); | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
examples/with-mux-video/app/(upload)/asset/[assetId]/layout.tsx
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,3 @@ | ||
export default function Layout({ children }: { children: React.ReactNode }) { | ||
return <div className="font-mono text-sm">{children}</div>; | ||
} |
3 changes: 3 additions & 0 deletions
3
examples/with-mux-video/app/(upload)/asset/[assetId]/loading.tsx
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,3 @@ | ||
export default function Loading() { | ||
return <div>Checking asset status...</div>; | ||
} |
51 changes: 51 additions & 0 deletions
51
examples/with-mux-video/app/(upload)/asset/[assetId]/page.tsx
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,51 @@ | ||
import Mux from "@mux/mux-node"; | ||
import { redirect } from "next/navigation"; | ||
import { Status } from "./types"; | ||
import AssetStatusPoll from "./AssetStatusPoll"; | ||
|
||
// reads MUX_TOKEN_ID and MUX_TOKEN_SECRET from your environment | ||
const mux = new Mux(); | ||
|
||
const checkAssetStatus = async (assetId: string): Promise<Status> => { | ||
const asset = await mux.video.assets.retrieve(assetId); | ||
|
||
// if the asset is ready and it has a public playback ID, | ||
// (which it should, considering the upload settings we used) | ||
// redirect to its playback page | ||
if (asset.status === "ready") { | ||
const playbackIds = asset.playback_ids; | ||
if (Array.isArray(playbackIds)) { | ||
const playbackId = playbackIds.find((id) => id.policy === "public"); | ||
if (playbackId) { | ||
redirect(`/v/${playbackId.id}`); | ||
} | ||
} | ||
} | ||
|
||
return { | ||
status: asset.status, | ||
errors: asset.errors, | ||
}; | ||
}; | ||
|
||
// For better performance, we could cache and use a Mux webhook to invalidate the cache. | ||
// https://docs.mux.com/guides/listen-for-webhooks | ||
// For this example, calling the Mux API on each request and then polling is sufficient. | ||
export const dynamic = "force-dynamic"; | ||
|
||
export default async function Page({ | ||
params: { assetId }, | ||
}: { | ||
params: { assetId: string }; | ||
}) { | ||
const initialStatus = await checkAssetStatus(assetId); | ||
return ( | ||
<AssetStatusPoll | ||
initialStatus={initialStatus} | ||
checkAssetStatus={async () => { | ||
"use server"; | ||
return await checkAssetStatus(assetId); | ||
}} | ||
/> | ||
); | ||
} |
6 changes: 6 additions & 0 deletions
6
examples/with-mux-video/app/(upload)/asset/[assetId]/types.ts
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,6 @@ | ||
import type Mux from "@mux/mux-node"; | ||
|
||
export type Status = { | ||
status: Mux.Video.Assets.Asset["status"]; | ||
errors: Mux.Video.Assets.Asset["errors"]; | ||
}; |
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,57 @@ | ||
import Link from "../_components/Link"; | ||
import { MUX_HOME_PAGE_URL } from "../constants"; | ||
|
||
export default function Layout({ | ||
children, | ||
}: Readonly<{ children: React.ReactNode }>) { | ||
return ( | ||
<> | ||
<header className="mb-8"> | ||
<h1 className="font-bold text-4xl lg:text-5xl mb-2"> | ||
Welcome to Mux + Next.js | ||
</h1> | ||
<p className="italic">Get started by uploading a video</p> | ||
</header> | ||
<p className="mb-4"> | ||
<Link | ||
href={MUX_HOME_PAGE_URL} | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
Mux | ||
</Link>{" "} | ||
provides APIs for developers working with video. | ||
<br /> | ||
This example is useful if you want to build: | ||
</p> | ||
<ul className="list-disc pl-8 mb-4"> | ||
<li>A video on demand service like Youtube or Netflix</li> | ||
<li> | ||
A platform that supports user uploaded videos like TikTok or Instagram | ||
</li> | ||
<li>Video into your custom CMS</li> | ||
</ul> | ||
<p className="mb-4"> | ||
Uploading a video uses the Mux{" "} | ||
<Link href="https://docs.mux.com/docs/direct-upload"> | ||
direct upload API | ||
</Link> | ||
. When the upload is complete your video will be processed by Mux and | ||
available for playback on a sharable URL. | ||
</p> | ||
<p> | ||
To learn more,{" "} | ||
<Link | ||
href="https://github.com/vercel/next.js/tree/canary/examples/with-mux-video" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
check out the source code on GitHub | ||
</Link> | ||
. | ||
</p> | ||
<hr className="my-8 bg-gray-500" /> | ||
{children} | ||
</> | ||
); | ||
} |
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,3 @@ | ||
export default function Loading() { | ||
return <div>Preparing upload...</div>; | ||
} |
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,60 @@ | ||
import Mux from "@mux/mux-node"; | ||
import MuxUploader from "./MuxUploader"; | ||
import { redirect } from "next/navigation"; | ||
|
||
// reads MUX_TOKEN_ID and MUX_TOKEN_SECRET from your environment | ||
const mux = new Mux(); | ||
|
||
const createUpload = async () => { | ||
const upload = await mux.video.uploads.create({ | ||
new_asset_settings: { | ||
playback_policy: ["public"], | ||
encoding_tier: "baseline", | ||
}, | ||
// in production, you'll want to change this origin to your-domain.com | ||
cors_origin: "*", | ||
}); | ||
|
||
return upload; | ||
}; | ||
|
||
const waitForThreeSeconds = () => | ||
new Promise((resolve) => setTimeout(resolve, 3000)); | ||
|
||
const redirectToAsset = async (uploadId: string) => { | ||
let attempts = 0; | ||
while (attempts <= 10) { | ||
const upload = await mux.video.uploads.retrieve(uploadId); | ||
if (upload.asset_id) { | ||
redirect(`/asset/${upload.asset_id}`); | ||
} else { | ||
// while onSuccess is a strong indicator that Mux has received the file | ||
// and created the asset, this isn't a guarantee. | ||
// In production, you might listen for the video.upload.asset_created webhook | ||
// https://docs.mux.com/guides/listen-for-webhooks | ||
// To keep things simple here, | ||
// we'll just poll the API at an interval for a few seconds. | ||
await waitForThreeSeconds(); | ||
attempts++; | ||
} | ||
} | ||
throw new Error("No asset_id found for upload"); | ||
}; | ||
|
||
// since we want to create a new upload for each visitor, | ||
// we disable caching | ||
export const dynamic = "force-dynamic"; | ||
|
||
export default async function Page() { | ||
const upload = await createUpload(); | ||
|
||
return ( | ||
<MuxUploader | ||
onSuccess={async () => { | ||
"use server"; | ||
await redirectToAsset(upload.id); | ||
}} | ||
endpoint={upload.url} | ||
/> | ||
); | ||
} |
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,14 @@ | ||
import LibLink from "next/link"; | ||
|
||
/** | ||
* | ||
* @param className this component does not merge className with the default classes -- it only appends -- so beware of duplicates | ||
*/ | ||
const Link = ({ className, ...rest }: React.ComponentProps<typeof LibLink>) => ( | ||
<LibLink | ||
className={`underline hover:no-underline focus-visible:no-underline text-red-600 ${className}`} | ||
{...rest} | ||
/> | ||
); | ||
|
||
export default Link; |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,2 @@ | ||
export const MUX_HOME_PAGE_URL = | ||
"https://www.mux.com?utm_source=create-next-app&utm_medium=with-mux-video&utm_campaign=create-next-app"; |
Binary file not shown.
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,3 @@ | ||
@tailwind base; | ||
@tailwind components; | ||
@tailwind utilities; |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.