React Player (WIP)

Hello!

Here’s my first simple streamlit component which adds a video player based on a full-featured react player component. To quote the description, it can play a variety of URLs, including file paths, YouTube, Facebook, Twitch, SoundCloud, Streamable, Vimeo, Wistia, Mixcloud, and DailyMotion.

The streamlit component is quite simple: it forwards a list of allowed props directly to the react component, and it can also get feedbacks from events triggered by the player. You can specify which events you want to listen to.

event = st.react_player("https://soundcloud.com/imaginedragons/demons", ["onPlay", "onPause", "onProgress"])
st.write(event)

if event.name == "onPlay":
    st.info("Playing...")

if event.name == "onPause":
    st.info("Paused!")

if event.name == "onProgress":
    st.progress(event.data["played"])

The snipped above renders like so:

Here's the component declaration part
import streamlit as st
from collections import namedtuple

ReactPlayer = st.declare_component(url="http://localhost:3001")

@ReactPlayer
def create_instance(f, url, events=[], key=None, **props):
    """Wrapper component for ReactPlayer.

    A React component for playing a variety of URLs, including file paths, YouTube, Facebook,
    Twitch, SoundCloud, Streamable, Vimeo, Wistia, Mixcloud, and DailyMotion.

    You can find ReactPlayer's documentation here: https://github.com/CookPete/react-player.
    Almost all props are forwarded to the react component. Methods are not supported.

    Parameters
    ----------
    url : str or list of str
        The url(s) of a video or song to play.
    events : list of str, optional
        Events to get update from.

    Default properties
    ------------------
    controls : bool
        Set to true.
    width : str
        Set to 100%.

    Returns
    -------
    ReactPlayerEvent
        Object holding an event name (obj.event) and data (obj.data).
        Both attributes can be set to `None`.

    """
    # Allowed props
    props = {prop: value for prop, value in props.items() if prop in [
        "playing", "loop", "controls", "light", "volume", "muted",
        "playbackRate", "width", "height", "style", "progressInterval",
        "playsinline", "pip", "playIcon", "config"
    ]}

    # Allowed events
    events = [event for event in events if event in [
        "onReady", "onStart", "onPlay", "onProgress", "onDuration",
        "onPause", "onBuffer", "onBufferEnd", "onSeek", "onEnded",
        "onError", "onEnablePIP", "onDisablePIP"
    ]]

    # Default prop values
    props.setdefault("controls", True)
    props.setdefault("width", "100%")

    event = f(url=url, events=events, key=key, **props) or {}

    ReactPlayerEvent = namedtuple("ReactPlayerEvent", ["name", "data"])
    return ReactPlayerEvent(event.get("name", None), event.get("data", None))
And the react component code
import React, { useEffect } from "react"
import Player from "react-player"
import {
  withStreamlitConnection,
  ComponentProps,
  Streamlit,
} from "./streamlit"

import "bootstrap/dist/css/bootstrap.min.css"
import "./streamlit.css"

function ReactPlayer(props: ComponentProps) {
  if ("events" in props.args) {
    props.args.events.forEach((event: string) => {
      props.args[event] = (data?: any) => {
        Streamlit.setComponentValue({
          name: event,
          data: data
        })
      }
    })

    delete props.args.events
  }

  useEffect(() => {
    Streamlit.setFrameHeight()
  })

  return <Player {...props.args} />
}

export default withStreamlitConnection(ReactPlayer)
4 Likes

That’s really cool, seems like it didn’t take much effort at all on the Python side! What did you think about how the process of generating the component? Is there anything you’d suggest to us from an API perspective, or to users who might want to create their own component?

Making that component was quite simple. I’m not a web developer, I have almost no experience with React, yet it was really easy to wrap a react component to use it in a streamlit app. Great work on this!

My real interrogation was regarding where would be the best place to filter props and events like I did. I’ve done it python-side for now, but maybe for some reason it’d be better to do that sort of filtering react-side. Later, it’d be great in a best practice doc to have some rationale on where some specific logic should be implemented.

Other than that, regarding the API, I should play a little bit more with it to see what could be improved.

2 Likes

Hey! Just a small update to say that the react component I’m using to render videos doesn’t work without an iframe with allow-same-origin.

However, as suggested by tim here, we could serve this component from another local server. And unlike Disqus, the auto-height issue shouldn’t matter here.