Build a Mobile Twitch Clone

Lanre A.
Lanre A.
Published February 18, 2020 Updated December 22, 2021

People long for connection, and technology has allowed us to connect with those who aren't physically near in increasingly many ways over the last few decades. With applications like text messaging, and then group messaging, with our music, photos and movies moving to the cloud, we've been able to share more and more across great distances.

What if we were to build an app that seamlessly combined two wildly popular technologies: sharing over chat and watching videos online? (Okay. Fine. Twitch already did that... But, can you do it better...?)

In this tutorial, we'll be describing how to build a Twitch-style app with Stream Chat's "livestream" functionality. The application we will build will include both a video view and chat view!

What we will be building

Prerequisites

The application will consist of two parts:

  • A server (the backend): We are going to be making use of an opensource API server, so we won’t be needing to build this ourselves. You can find the server on Stream's own Nick Parsons' Github!
  • A client (the frontend): this what we'll build using React Native.

Creating a Stream Application

Before proceeding with the tutorial, you will need to get your credentials from Stream Chat. Once you’ve created your account, you can grab your credentials from Stream by:

  • Navigating to your Stream Dashboard
  • Creating a new application
  • Clicking on "Chat" in the top navigation
  • Scrolling down to "App Access Keys", where you can view your credentials

Once you've found them, save them somewhere safe, as they will be used to build the server!

Setting Up Your Environment

We need to create a directory to hold both the API and the mobile app. We can do this simply by executing the following command:

sh
1
mkdir twitch-stream-app

Well, that was easy...

Setting Up the Server

As mentioned earlier, we are not going to be building the server, as that has been done and open sourced (no use in recreating the wheel...). With that said, we will need to configure it a bit:

sh
1
2
3
4
cd twitch-stream-app git submodule add git@github.com:nparsons08/stream-chat-api.git server cd server cp .env.example .env

Now that you've created your .env file, update it with secure details, like your credentials, that you don't want flying around on the interwebs.

Since we will be building a "livestream" application with Stream Chat, we will also need to make a tiny, simple, one-line change to the file located at server/src/controllers/v1/auth/init.action.js:

js
1
const channel = await client.channel('livestream', 'General');

If you're curious about what other types of channels you can create, all supported channel types can be found here.

Once the above has been updated, we can run the server:

sh
1
2
yarn && yarn build yarn start

Setting Up the Client

To set up the client, we'll create a new directory to house the application, and then create a new React Native project, which will reside in the client directory. Finally, we'll add the opensource Stream server API project as a git submodule. All of this can be done using this series of commands:

sh
1
2
3
cd twitch-stream-app git init react-native init client

Building the Client

The next step is to build the actual app. To get started, let's install a few packages:

sh
1
2
3
4
5
6
7
8
cd client yarn add axios react-native-video stream-chat stream-chat-react-native yarn add @react-native-community/netinfo yarn add react-native-image-picker react-native-document-picker cd ios && pod install && cd .. react-native link react-native-image-picker react-native link react-native-document-picker react-native link @react-native-community/netinfo

Once all of our packages have been installed, we can move on to building out the screens! The application is going to have 2 pages:

  • The Authentication Page: The user will be asked to provide his/her email address and password. This doubles as the login and registration process. If the email already exists, the provided password will be checked against what exists in the database, after which (s)he will be logged into the application. Else, a new user is added to the database.
  • The Video and Chat Page: This page is a split view, containing both the video currently showing and the livestream chat with other users.

The authentication page is going to be the entry page of the app. By default, React Native includes an App.js file (which you now have, after running the nifty commands above); you will need to overwrite that file with the following content:

import React, {Component} from 'react';
import {Alert, StyleSheet, View, SafeAreaView} from 'react-native';
import {StreamChat} from 'stream-chat';
import Signup from './Signup';
import VideoPlayer from './VideoPlayer';
import Chat from './Chat';
import axios from 'axios';

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isAuthenticated: false,
      id: '',
    };

    this.chatClient = new StreamChat('STREAM_KEY_FROM_DASHBOARD');
  }

  onLoginCallBack = user => {
    if (user.email.length === 0) {
      Alert.alert('Login', 'Please provide your email');
      return;
    }

    if (user.password.length === 0) {
      Alert.alert('Login', 'Please provide your password');
      return;
    }

    user = {
      ...user,
      name: {
        first: 'Bot',
        last: 'Last Name',
      },
    };

    axios
      .post('http://localhost:5200/v1/auth/init', user, {
        headers: {Authorization: 'AUTHORIZATION_HEADER'},
      })
      .then(res => {
        this.chatClient.setUser(
          {
            id: res.data.user._id,
            username: res.data.user.email,
            image:
              'https://stepupandlive.files.wordpress.com/2014/09/3d-animated-frog-image.jpg',
          },
          res.data.token
        );
        this.setState({
          isAuthenticated: true,
          id: res.data.user._id,
        });
      })
      .catch(err => {
        console.log(err);
        Alert.alert('Login', 'Could not log you in');
      });
  };

  render() {
    return (
      <SafeAreaView style={{flex: 1}}>
        <View style={{flex: 1}}>
          {!this.state.isAuthenticated || this.state.currentUser === null ? (
            <View style={styles.container}>
              <Signup cb={this.onLoginCallBack} />
            </View>
          ) : (
            <View style={[{flex: 1}]}>
              <View style={{flex: 0.4}}>
                <VideoPlayer />
              </View>
              <View style={{flex: 0.6}}>
                <Chat userID={this.state.id} chatClient={this.chatClient} />
              </View>
            </View>
          )}
        </View>
      </SafeAreaView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

If you take a look at lines 4 through 6, you will notice we import three local files which do not exist yet; let's create those:

sh
1
touch Signup.js VideoPlayer.js Chat.js

In the newly created Signup.js file, you will need to copy and paste the following code:

import React, {Component} from 'react';
import {Alert, Button, TextInput, View, StyleSheet} from 'react-native';

export default class Login extends Component {
  constructor(props) {
    super(props);

    this.state = {
      email: '',
      password: '',
    };
  }

  render() {
    return (
      <View style={styles.container}>
        <TextInput
          value={this.state.email}
          onChangeText={email => this.setState({email: email.trim()})}
          placeholder={'Email address'}
          style={styles.input}
        />

        <TextInput
          value={this.state.password}
          onChangeText={password => this.setState({password: password.trim()})}
          placeholder={'Password'}
          style={styles.input}
          secureTextEntry={true}
        />
        <Button
          title={'Login'}
          style={styles.input}
          onPress={() => {
            this.props.cb(this.state);
          }}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  input: {
    width: 200,
    height: 44,
    padding: 10,
    borderWidth: 1,
    borderColor: 'black',
    marginBottom: 10,
  },
});

In the newly created VideoPlayer.js, you'll need to add the content below:

import React, {Component} from 'react';
import {StyleSheet} from 'react-native';
import Video from 'react-native-video';

export default class VideoPlayer extends Component {
  render() {
    return (
      <Video
        source={{
          uri:
            'https://file-examples.com/wp-content/uploads/2017/04/file_example_MP4_480_1_5MG.mp4',
        }}
        ref={ref => {
          this.player = ref;
        }}
        style={styles.backgroundVideo}
        controls={true}
      />
    );
  }
}

var styles = StyleSheet.create({
  backgroundVideo: {
    position: 'absolute',
    top: 0,
    left: 0,
    bottom: 0,
    right: 0,
  },
});

Finally, Chat.js should be updated to contain the following:

import React, {Component} from 'react';
import {SafeAreaView, View} from 'react-native';
import {
  Chat,
  Channel,
  MessageList,
  MessageInput,
} from 'stream-chat-react-native';

export default class ChatView extends Component {
  render() {
    const channel = this.props.chatClient.channel('livestream', 'General');
    channel.watch();

    return (
      <Chat client={this.props.chatClient}>
        <Channel channel={channel}>
          <View style={{display: 'flex', height: '100%'}}>
            <MessageList />
            <MessageInput />
          </View>
        </Channel>
      </Chat>
    );
  }
}

Great work! Before you move on, make sure the following values have been updated to match your actual credentials:

  • STREAM_KEY_FROM_DASHBOARD : The actual key you copied from the dashboard.
  • AUTHORIZATION_HEADER: This can be found in the .env file in the server directory

Once you've completed all of the steps above, you can spin up the app by running the following command:

sh
1
2
react-native run-ios react-native run-android

The above command will start a new simulator with the application running! You can manually start another simulator and run them side by side using the following command:

sh
1
react-native run-ios --simulator "iPhone 8 Plus"

With your two simulators running, you should have something like this:
User-uploaded image: v4P18BNcSh.gif

Wrapping Up

In this tutorial, we made use of Stream Chat to build a fully-functional chat application that allows users to communicate while watching a video. One way in which you can take your app even further is to make the video part of the application live (e.g. all users regardless of when they log into the application view the same thing rather than watching the video from the start). What else would you do to improve this app?

If you got a little stuck, or are simply curious, you can find the full source code for our app on GitHub.

Thanks for joining us, and happy coding!

Integrating Video With Your App?
We've built a Video and Audio solution just for you. Check out our APIs and SDKs.
Learn more ->