DEV Community

loading...
Cover image for Making User Generated Video Slideshows in Redwood with Cloudinary

Making User Generated Video Slideshows in Redwood with Cloudinary

Milecia
Starting classes soon! | Software/Hardware Engineer | International tech speaker | Random inventor and slightly mad scientist with extra sauce
・9 min read

Videos are one of the most popular forms of content online. They help people start new professions and they keep us endlessly entertained. That's why you have to make sure any video-based apps you work on give users a good experience.

Users like it when they can customize videos to display what they need. In this tutorial, you'll learn how to let your users make custom video slideshows with Redwood and Cloudinary.

Setting up media

We're going to be working with Cloudinary to handle our media because they make it easy to work with. To start, make sure you have a Cloudinary account. Then go to the Media Library and upload any images and videos you want users to be able to select from.

You'll also need to upload the template for a slideshow to your Media Library. Right now, you have to download this slideshow template because it's the only one supported by the slideshow generation functionality.

Before we jump into the code, it'll help to take a look at the URL we'll be working with to generate these custom slideshows. You can also find an explanation of this URL in the docs.

https://res.cloudinary.com/`cloudname`/video/upload/fn_render:`global-settings`;vars_(`slide-settings`(`individual-slide`))/`global-transformations`/`template`.`ext`
Enter fullscreen mode Exit fullscreen mode

You can find the cloudname in your Cloudinary console.

cloud name in Cloudinary console

  • global-settings: general settings applied to your video, like the height and width that Cloudinary should return as well as the duration that the video should be.
  • slide-settings: settings that apply to each slide. This will include things like how long the image is shown before switching to the next one, what kind of transition is applied between slides, and how long the transition lasts.
  • individual-slide: how you add images or videos based on their public_id to your slideshow. We'll use this to add the specific images we want a little later.
  • global-transformations: how you can use the regular transformations Cloudinary has available, like automatically adjusting the quality of the video so that it's optimized for your users.
  • template: how we let Cloudinary know we're going to create a video slideshow. This is the template we downloaded earlier.

This is what all of our work is leading up to: letting the user control their own slideshow.

Creating the customizations

We're going to let users define which videos are in the slideshow, the duration of the slideshow, the width of the video, the duration of each slide, the height of the video, and the duration of each transition. Let's set up the Redwood app with the following command.

yarn create redwood-app ./user-generated-slideshows

This will make a fresh Redwood project for you. We'll be working in the api directory to handle the database and back-end and the web directory to handle the front-end.

We'll start by making a new schema for our Postgres database.

If you want to follow along with the database set up, you'll need to have Postgres installed locally.

In the api > db directory, open the schema.prisma file. The first thing we'll do in here is update the database provider. Right now it's set to sqlite. We need to update this value to postgresql.

You'll also see an example of a schema model here and we'll be replacing that with the following.

model Property {
  id                 Int      @id @default(autoincrement())
  width              Int
  height             Int
  videoDuration      Int
  slideDuration      Int
  transitionDuration Int
  videos             String[]
}
Enter fullscreen mode Exit fullscreen mode

This defines all of the properties of the slideshow a user can set. There are a few more things we need to do to make sure our database gets set up correctly. In the root of the project, open the .env file. There's a line assigning a value to DATABASE_URL and it's commented out.

Uncomment that line and update the URL to the connection string for your local Postgres database instance. It might look something like this.

DATABASE_URL=postgres://postgres:admin@localhost:5432/slideshows

Now you have everything in place to run a database migration! To do that, run the following command.

yarn rw prisma migrate dev

This will get Prisma to generate your migration and update the database according to your schema definition. We can move on to the back-end and front-end now.

Making the back-end

Now it's time to create a few things to get the back-end working. First, we'll generate a GraphQL schema and a resolver with the following command.

yarn rw g sdl property

Going to the api > src > graphql folder will show you a new sdl file. This holds all of the GraphQL types you need to get started. Since we're already in this file, let's add a new mutation type. This mutation will create a new set of properties when the user submits the form we'll create on the front-end later.

Below the UpdatePropertyInput type, add the following mutation type.

type Mutation {
  createProperty(input: CreatePropertyInput): Property
}
Enter fullscreen mode Exit fullscreen mode

This adds the create mutation to our GraphQL definitions. Now we need to add the actual functionality that will update the database.

Go to api > src > services and you'll see a properties folder. The files inside this folder were generated when we ran that yarn rw g sdl property command earlier. It has a resolver for fetching all of the properties from the database. Now we need to add a resolver that will handle the creation of a property.

At the bottom of the properties.js file, add the following code for the create resolver.

export const createProperty = ({ input }) => {
  return db.property.create({ data: input })
}
Enter fullscreen mode Exit fullscreen mode

That's it for the back-end! So far we have the database created and connected the GraphQL resolvers to it. All that's left is for us to make a front-end for users to interact with.

Building the front-end

Let's make a home page that displays the video slideshow and the form with the options they choose. We can let Redwood generate a new page and add the new routing with this command.

yarn rw g page home /

If you go to web > src > pages, you'll see the HomePage folder. The files inside were created with the command we just ran. If you take a look at Routes.js, you'll see the new route for the home page is already there.

Then we'll update the HomePage component to display the form users will use to make their custom slideshow.

Adding the form

We'll need a form to get the info we need to make these custom slideshows. Redwood has its own form helpers to make them easier to work with. In the HomePage component, we'll import a few things at the top of the file.

You can delete the existing imports because we won't be using them.

import { Form, Label, TextField, Submit } from '@redwoodjs/forms'
import { useMutation } from '@redwoodjs/web'
Enter fullscreen mode Exit fullscreen mode

We'll use these imports to make our form and send the new property to the database through the GraphQL server. Let's add the form first.

You can delete everything inside the HomePage component. We'll be adding completely new elements.

const HomePage = () => {
  return (
    <Form onSubmit={onSubmit}>
      <Label name="video1">First video</Label>
      <TextField name="video1" />
      <Label name="video2">Second video</Label>
      <TextField name="video2" />
      <Label name="video3">Third video</Label>
      <TextField name="video3" />
      <Label name="width">Video width (px)</Label>
      <NumberField name="width" max={500} />
      <Label name="height">Video height (px)</Label>
      <NumberField name="height" max={500} />
      <Label name="videoDuration">Video duration (ms)</Label>
      <NumberField name="videoDuration" max={11500} />
      <Label name="slideDuration">Slide duration (ms)</Label>
      <NumberField name="slideDuration" max={5500} />
      <Label name="transitionDuration">Transition duration (ms)</Label>
      <NumberField name="transitionDuration" max={5000} />
      <Submit>Save</Submit>
    </Form>
  )
}
Enter fullscreen mode Exit fullscreen mode

We've made all of the fields to match the data we need to store in the database. That means it's time to bring in the GraphQL so that we send these values correctly. This is how we'll create a new property. Inside of the HomePage component, add this code above the return statement.

const [createProperty] = useMutation(CREATE_PROPERTY)

const onSubmit = (data) => {
  const videos = [data.video1, data.video2, data.video3]
  createProperty({
    variables: {
      width: data.width,
      height: data.height,
      videoDuration: data.videoDuration,
      slideDuration: data.slideDuration,
      transitionDuration: data.transitionDuration,
      videos: videos,
    },
  })
}
Enter fullscreen mode Exit fullscreen mode

Here, we make a mutation out of CREATE_PROPERTY which we'll be making right after this and we make the onSubmit for the form to handle the GraphQL call. At the bottom of the file, above the export statement add this code.

const CREATE_PROPERTY = gql`
  mutation CreateProperty(
    $width: Int!
    $height: Int!
    $videoDuration: Int!
    $slideDuration: Int!
    $transitionDuration: Int!
    $videos: [String]!
  ) {
    createProperty(
      input: {
        width: $width
        height: $height
        videoDuration: $videoDuration
        slideDuration: $slideDuration
        transitionDuration: $transitionDuration
        videos: $videos
      }
    ) {
      id
      width
      height
      videoDuration
      slideDuration
      transitionDuration
      videos
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

This GraphQL mutation is what we use in the useMutation method to create a property. It passes all of the data to the resolver.

If you run the app with yarn rw dev, you should see something like this in your browser.

form on home page

Go ahead and create a new property and save it. This will help us later in the tutorial.

Adding the video

We can finally add the video to the page! Earlier we looked at the slideshow generation URL, now we're going to use it with a few placeholder values. First, we'll create a new variable under the mutation definition.

const properties = {
  width: 500,
  height: 500,
  videoDuration: 15,
  slideDuration: 3000,
  transitionDuration: 1000,
  videos: ['beach-boat', '3dogs', 'reindeer'],
}
Enter fullscreen mode Exit fullscreen mode

This gives us some default values to start with so that our video will load. Now we'll actually add the video. Below the form, add the following code.

You'll need to wrap the form and the video in a fragment so that React stays happy.

<video controls>
  <source
    src={`https://res.cloudinary.com/milecia/video/upload/fn_render:w_${
      properties.width
    };h_${properties.height};du_${properties.videoDuration};vars_(sdur_${
      properties.slideDuration
    };tdur_${
      properties.transitionDuration
    };transition_s:circlecrop;slides_(${properties.videos
      .map((mediaDoc) => `(media_i:${mediaDoc})`)
      .join(';')}))/f_auto,q_auto/slideshow_hlhpzw.mp4`}
    type="video/mp4"
  />
  Your browser does not support the video tag.
</video>
Enter fullscreen mode Exit fullscreen mode

This URL looks pretty crazy since we've added a bunch of extra parentheses and curly braces, but it's just loading the values into the placeholders we described earlier.

If you run your app again, your home page should look similar to this.

home page with form and video

So you have the form in place and the video displaying on the page. The only thing left is to load in a property so that the user sees their custom slideshow.

Loading the saved properties

We'll need to add one more GraphQL query to the front-end and we'll be able to set our initial video state. First, we'll add a new method to an existing import.

import { useMutation, useQuery } from '@redwoodjs/web'
Enter fullscreen mode Exit fullscreen mode

Then we'll create a query inside of the HomePage component, just above the mutation.

const { loading, data } = useQuery(PROPERTIES)
Enter fullscreen mode Exit fullscreen mode

Next we'll add PROPERTIES just above our CREATE_PROPERTY mutation at the bottom of the file.

const PROPERTIES = gql`
  query Properties {
    properties {
      id
      width
      height
      videoDuration
      slideDuration
      transitionDuration
      videos
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

The generates the GraphQL query that we need to fetch all of the properties from the database. Now let's update our default variable to read the latest property that was added.

const properties = data.properties[data.properties.length - 1]
Enter fullscreen mode Exit fullscreen mode

To make sure we have data to read, we're going to use the loading variable we received from useQuery. Right above the last line of code we wrote, add this.

if (loading) {
  return <div>Loading..</div>
}
Enter fullscreen mode Exit fullscreen mode

This is going to prevent the app from crashing while the data is loading. Now if you refresh your app, you should see a slideshow generated with the values you saved earlier! You can try it out with a new value if you want and then reload the page.

Keep in mind that video slideshow generation is still a beta feature so it might be a little buggy sometimes.

Finished code

You can check out the finished code in this Code Sandbox or in this GitHub repo in the 'user-gen-vids` folder.

Conclusion

Adding customizations can make it easier for people to learn and interact with your site. Video content is not slowing down so giving your users more control can be something that gives your site an edge over the others. Plus, working with Redwood will hopefully make it easier for you to keep things up to date!

Discussion (0)