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!
Prerequisites
- A Stream account
- A working React Native installation
- MongoDB
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:
1mkdir 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:
1234cd 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
:
1const 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:
12yarn && 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:
123cd 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:
12345678cd 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:
1touch 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 theserver
directory
Once you've completed all of the steps above, you can spin up the app by running the following command:
12react-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:
1react-native run-ios --simulator "iPhone 8 Plus"
With your two simulators running, you should have something like this:
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!