Build a YouTube Queue with Next.js: Part 1
Khalea B. / June 15, 2021
7 min read
In recent months, I've become fascinated with shared, online spaces. Applications like Clubhouse, Discord, and lesser-known platforms such as Beatsense have been integral to maintaining a sense of community throughout the pandemic.
In this tutorial, we'll be creating a shared space where you can add & watch YouTube videos to a queue, and chat in real time with other users. In part 1, we'll focus on the videos.
Prerequisites
- Familiarity with React
- Familiarity with Next.js
- Node.js 10.13 or later
- create-next-app (
npm i create-next-app
)
Getting Started
First you'll want to initialize a new Next.js project and set up TailwindCSS. In the command line, navigate to the location of your choice and run:
npx create-next-app -e with-tailwindcss youtube-social
To install Tailwind and its dependencies, navigate to the youtube-social
folder in the CLI and run:
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
Then generate the default configuration files tailwind.config.js
and postcss.config.js
:
npx tailwindcss init -full -p
To start the development server run:
npm run dev
When you go to http://localhost:3000, you should see the default "Welcome to Next.js" page.
Player Component
We'll be using the nifty react-player/youtube library created by Pete Cook to easily handle video events related to the queue of YouTube videos.
First, use the CLI to install react-player
in your project:
npm install react-player
Create a new folder in the root level of your project called components
. We'll store independent 'components' of the app in this folder, making them reusable and easy to modify.
Add a new file called player.js
into the components
folder. This will contain the YouTube embed.
In player.js
, import react-player/youtube
like so:
import ReactPlayer from 'react-player/youtube'
Next, create a functional component called Player
that accepts props. The props we'll pass in are a YouTube video ID and a callback function to be executed when a video ends.
Within it, declare a variable called videoURL that holds the base YouTube video URL and the video ID, and return a <div>
containing a <ReactPlayer>
:
export default function Player(props) {
const videoURL = "https://www.youtube.com/watch?v=" + props.videoId
return(
<div>
<ReactPlayer />
</div>
)
}
The ReactPlayer accepts a variety of props that can help us manage events related to the player. We will use:
url
: (String) The url of the YouTube video
playing
: (Boolean) Video plays automatically
onEnded
: (Callback) Function that executes when the video ends
config
: (JSON) Options for the YouTube embed
export default function Player(props) {
const videoURL = "https://www.youtube.com/watch?v=" + props.videoId
return(
<div>
<ReactPlayer
url={videoURL}
playing={true}
onEnded={props.onEnd}
config={{
youtube: {
playerVars: {
autoplay: 1,
controls: 1
}
}
}}
/>
</div>
)
}
Import the Player
component into index.js
. Remove all of the boilerplate code within the outer <div>
s. Place the Player under the <main>
section of the page. As a placeholder, we'll pass in a hard-coded videoId
for a Kurzgesagt video:
import Head from 'next/head'
import Player from '../components/player'
export default function Home() {
return (
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<Head>
<title>YouTube Social</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<Player videoId={"JXeJANDKwDc"}/>
</main>
</div>
)
}
Save your work in the editor, and the page should reload and start playing the video.
Queue Component
Now, let's set up the queue component. We want to take JSON formatted video data from the YouTube API and extract the video IDs, titles, and channel titles and display them as part of the queue.
We'll start with some hard coded data that mimics the YouTube API response. Create a folder called data
in the root of your project. Then add a file called test-queue.json
into it, and copy the following test data:
{
"items": [
{
"id": {
"videoId": "3qqzz9a8pMQ"
},
"snippet": {
"title": "Grimes - Aeon / Kill V. Maim / Di-Li-Do (Acapella) [Mixed]",
"channelTitle": "sef",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/3qqzz9a8pMQ/default.jpg",
"width": 120,
"height": 90
}
}
}
},
{
"id": {
"videoId": "HlLx7oE7q3I"
},
"snippet": {
"title": "Hozier - Dinner & Diatribes (Official Video)",
"channelTitle": "HozierVEVO",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/Iq5gesj6kmw/default.jpg",
"width": 120,
"height": 90
}
}
}
},
{
"id": {
"videoId": "M9teCJVTr_s"
},
"snippet": {
"title": "Yves Tumor - Licking An Orchid (ft. James K)",
"channelTitle": "Yves Tumor",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/M9teCJVTr_s/default.jpg",
"width": 120,
"height": 90
}
}
}
}
,
{
"id": {
"videoId": "p2Rro6TQgpU"
},
"snippet": {
"title": "FKA twigs - home with you",
"channelTitle": "FKA twigs",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/p2Rro6TQgpU/default.jpg",
"width": 120,
"height": 90
}
}
}
},
{
"id": {
"videoId": "286jXjwdst0"
},
"snippet": {
"title": "Apashe ft. Instasamka - Uebok (Gotta Run) [Official Video]",
"channelTitle": "Kannibalen Records",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/gM4xZy39kNE/default.jpg",
"width": 120,
"height": 90
}
}
}
},
{
"id": {
"videoId": "xqYFU1_SiBo"
},
"snippet": {
"title": "Untitled (2012 Remaster)",
"channelTitle": "Interpol",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/xqYFU1_SiBo/default.jpg",
"width": 120,
"height": 90
}
}
}
}
]
}
Add a new component called queue.js
to the components
folder. The queue
component should accept a prop called data
containing the video data. The data will be mapped to unique rows like so:
export default function Queue(props) {
return (
<div className="max-w-sm max-h-96 px-16 overflow-y-scroll">
<h3 className="font-bold">Queue</h3>
{
props.data.map((item, index) => {
return (
<div key={index} className="flex flex-row items-center w-60">
<h1 className="text-lg">{index + 1}</h1>
<div className="text-sm">
<h3 className="font-bold">{item.snippet.title}</h3>
<p>{item.snippet.channelTitle}</p>
</div>
</div>
)
})
}
</div>
)
}
We'll want to use the useState
hook in order to track and manage the state of the video data. useState
will help us dynamically update the Queue
component once videos are removed from the video data list.
In the index.js
file, import the TestQueue
, the Queue
component, and useState
:
import TestQueue from '../data/test-queue.json'
import Queue from '../components/queue'
import { useState } from React
In the Home
component, add the useState
hook with variables called videoData
and updateVideoData
. Set the initial state of videoData
to TestQueue.items
:
const [videoData, updateVideoData] = useState(TestQueue.items)
We'll place the Player
and Queue
components side by side using Flexbox with Tailwind. We'll also pass the videoData
to the Queue
component as a prop. This will keep the Queue
component updated as videoData
changes.
<main>
<div className="flex flex-row">
<Player videoId={"JXeJANDKwDc"}/>
<Queue data={videoData}/>
</div>
</main>
Player events
You may notice that the first video plays to completion, but the next video in the queue does not automatically start once it ends. To enable this behavior, we need to use the onEnd
prop of the Player
to pass in a callback that initiates a shift in the queue, and the current video.
Let's start by adding a useState
hook for the currentVideoId
in our index.js
file. Set the default value to the first video in the queue:
const [currentVideoId, updateCurrentVideoId] = useState(videoData[0].id.videoId)
Now pass the currentVideoId
down to the Player
component:
<Player videoId={currentVideoId}/>
Next up is the onEnd
callback. We need to create a function that updates the queue and the current video, then pass it to the Player
via the onEnd
prop:
const playNext = () => {
videoData.shift()
if (videoData.length > 0) {
updateCurrentVideoId(videoData[0].id.videoId)
}
}
<Player videoId={currentVideoId} onEnd={playNext}/>
Conclusion
Voila, you now have a basic YouTube playlist application! In the next part, I'll show you how to use the YouTube Data API to implement search. This code is also available on Github.