0

I have multiple videos in the swiper to show videos one by one, but all the videos are loaded and playing at the same time and audios are messed up, I want current video only play at a time.

import * as React from 'react';
import { Text, View, StyleSheet,Image, Dimensions } from 'react-native';
import { Constants } from 'expo';
import { Video } from 'expo';
import Swiper from './Swiper';
import InViewPort from './InViewport';
const screenWidth = Dimensions.get('window').width ;
const screenHeight = Dimensions.get('window').height;

export default class App extends React.Component {
  constructor(props) {
    super(props);
    // Your source data
    this.state = {
      images: {},
      muted : false,
      paused: true,
    };
    this.player = Array();
    this.onChangeImage = this.onChangeImage.bind(this);
  }

   videoError(err){
     console.warn(err);
   }

   pauseVideo = () => {
     var curr = this.state.currentIndex;
     console.warn(curr);
     if(this.player[curr]) {
       this.setState({paused: true });
     }
   }

   playVideo = () => {
     var curr = this.state.currentIndex;
     console.warn(curr);
     if(this.player[curr]) {
     this.setState({paused: false});
     }
   }

   handlePlaying = (isVisible) => {
     isVisible ? this.playVideo() : this.pauseVideo();
   }


  onChangeImage (index) {
     this.setState({ currentIndex: index});
   }


  render() {
   let items = Array.apply(null, Array(15)).map((v, i) => {
      return {
        id: i,
        caption: i + 1,
        source: { uri: 'http://placehold.it/200x200?text=' + (i + 1) },
        dimension: '{ width: 150, height: 150 }',
      };
    });
      return(
        <View style={styles.DefaultView}>
          <Swiper
          showsPagination={false}
          onIndexChanged={this.onChangeImage}
          index={0}
          >
           {items.map((item, key) => {
             if(key==1 || key ==5){
               return (
                 <InViewPort onChange={this.handlePlaying} key={key}>
                  <Video onError={this.videoError}
                   muted={this.state.muted}
                   paused={this.state.paused}
                   source={{uri: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4' }}
                   style={styles.backgroundVideo}
                   ref={(ref) => {
                      this.player[key] = ref;
                    }}
                    controls={true}
                   />
                  </InViewPort>
               )
             }else{
               return(
                 <Image
                   resizeMode='contain'
                   style={{width:screenWidth, height: screenHeight}}
                   source={item.source}
                   key={key}
               />
              )
             }
           })}
         </Swiper>
         </View>
      )
  }
}

const styles = StyleSheet.create({
    scrollView: {
        flex: 1,
        flexDirection: 'row',
    },
    DefaultView: {
        flex: 1,
        backgroundColor: '#000',
        width: screenWidth,
        justifyContent:'center',
        alignItems:'center'
    },
    iconContainer: {
      flexDirection: "row",
      justifyContent: "space-evenly",
      width: 150,
    },
    backgroundVideo: {
      position: 'absolute',
      top: 0,
      left: 0,
      bottom: 0,
      right: 0,
      width: screenWidth,
      height: 300,
      marginTop:'50%',
      position:'absolute',
  },
});

I need some idea on this, we have a player reference to be used, also swiper component have onIndexChanged which will trigger when we moved to next video, how we can link the reference of the player to onIndexChanged and when we do swipe how we make it current video only to play?

As per Andrew suggestion I have used InPortView component too determine the current view of swipe, but still I am not sure how to make reference for video elements to be used in the functions for play and pause the concern video.

Components used:

For video react-native-video

For Swiper : react-native-swiper

Updated Full code with Expo example : Expo Snack

Jothi Kannan
  • 3,320
  • 6
  • 40
  • 77

1 Answers1

5

So taking your snack. I managed to get it to work.

I moved the Video out into its own component and passed a few additional props to it, the index in the array and the currentIndex showing.

export default class App extends React.Component {
  constructor(props) {
    super(props);
    // Your source data
    this.state = {
      images: {},
      muted : false,
      paused: true,
      currentIndex: 0
    };
  }

  onChangeImage = (index) => {
     console.log('currentIndex ', index)
     this.setState({ currentIndex: index});
   }

  render() {
   let items = Array.apply(null, Array(15)).map((v, i) => {
      return {
        id: i,
        caption: i + 1,
        source: { uri: 'http://placehold.it/200x200?text=' + (i + 1) },
        dimension: '{ width: 150, height: 150 }',
      };
    });
      return(
        <View style={styles.DefaultView}>
          <Swiper
          showsPagination={false}
          onIndexChanged={this.onChangeImage}
          index={0}
          >
           {items.map((item, key) => {
             if(key==1 || key ==5){
               return (
                 <VideoPlayer key={key} index={key} currentIndex={this.state.currentIndex}/>
               )
             }else{
               return(
                 <Image
                   resizeMode='contain'
                   style={{width:screenWidth, height: screenHeight}}
                   source={item.source}
                   key={key}
               />
              )
             }
           })}
          </Swiper>
         </View>
      )
  }
}

The video component uses react-native-inviewport to help handle whether or not it is in the viewport. However it doesn't play nicely with react-native-swiper but it is possible to get it to work.

export default class VideoPlayer extends React.Component {

  pauseVideo = () => {
    if(this.video) {
      this.video.pauseAsync();
    }
  }

  playVideo = () => {
    if(this.video) {
      this.video.playAsync();
    }
  }

  handlePlaying = (isVisible) => {
    this.props.index === this.props.currentIndex ?  this.playVideo() : this.pauseVideo();
  }

  render() {
      return (
        <View style={styles.container}>
          <InViewPort onChange={this.handlePlaying}>
            <Video
              ref={ref => {this.video = ref}}
              source={{ uri: 'http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4' }}
              rate={1.0}
              volume={1.0}
              isMuted={false}
              resizeMode="cover"
              shouldPlay
              style={{ width: WIDTH, height: 300 }}
            />
          </InViewPort>
        </View>
      )
  }  
}

When I used the InViewPort component alone it seemed to think that the video in position 6 was in the viewport and would play it. So what I use the InviewPort is to perform a check to compare the index of the video with the currentIndex if they match play the video otherwise pause. I suppose this could be updated to use componentDidUpdate to handle the changes in the props. However, additional checks will need to be performed when the component mounts so that it doesn't play the video.

Here is my snack with it working. https://snack.expo.io/@andypandy/swiper-video

Andrew
  • 26,706
  • 9
  • 85
  • 101
  • No worries. I am glad that it is working for you. It's not a perfect solution but it seems to do the job. I had a look at trying to do it with `componentDidMount` and `componentDidUpdate` instead of using the `InViewPort` component but it still played the video when on index 0. I am sure it is possible to do it with them but it would take further looking into. – Andrew Feb 24 '19 at 09:10
  • Please update once you get the result, it would be very helpful. As a newbie, I need to know whatever the methods we can do to get this done. please – Jothi Kannan Feb 24 '19 at 09:13
  • Not sure if this changes with hooks but when a apply the props onIndexChanged I get last screen on stack with a ugly flash – I'm not human May 04 '20 at 22:03