1

I have flatlist having images, videos. for images, I defined duration to show and then move to the next item in flatlist, but in case of the video once a video ended then move to next item.

I am using Redux for currentlyPlayingIndex and flatlist datasource. If I have only images in flatlist it is working fine, but if I have a video, on video end I need to pass event from child to its parent. The parent calls the same method to move to the next index as for image duration end but the increment of currentlyPlayingIndex by one is not happening in case of video end. parent component code or flatlist handler

 import React, { Component } from 'react'
import { TouchableWithoutFeedback, View,StatusBar,Dimensions,FlatList } from 'react-native'
import {FileType,getFileType,getFileExtension} from '../services/FileManagerService'
import VideoPlayer from '../components/PlayerTypes/VideoPlayer'
import ImagePlayer from '../components/PlayerTypes/ImagePlayer'
import PdfPlayer from '../components/PlayerTypes/PdfPlayer'
import { ScaledSheet } from 'react-native-size-matters';

import NothingTpPlay from '../components/PlayerTypes/NothingTpPlay'
//redux
import {bindActionCreators} from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../database/actions/ScheduleActions.js'; //Import your actions
import * as Animatable from 'react-native-animatable';
import constants from '../config/constants'
import KeepAwake from 'react-native-keep-awake';
import AudioPlayer from '../components/PlayerTypes/AudioPlayer'
import { showToastMessage } from '../utils/ToastMessage'
import I18n from "../locales/i18n-js";
import WebsitePlayer from '../components/PlayerTypes/WebsitePlayer'
import FullScreen from "../NativeBridgingHeader/FullScreen";

let deviceWidth = Dimensions.get('window').width
let deviceHeight = Dimensions.get('window').height


 class PlaylistPlayerScreen extends Component {
    constructor() {
        super();
        this.state = {
          currentVisibleIndex:-1
        }
      this.playNextFile = this.playNextFile.bind(this)
      this.videoEnded = this.videoEnded.bind(this)
        this.schedulePlayDurationTimer = this.schedulePlayDurationTimer.bind(this)
        this.viewabilityConfig = {
          waitForInteraction: false,
          itemVisiblePercentThreshold: 99,
        }
      }

    static navigationOptions = {
        header: null,
    };

    onViewableItemsChanged = ({ viewableItems }) => {
      // viewableItems will show you what items are in view
      // console.log("onViewableItemsChanged called" + JSON.stringify(viewableItems))
       if(viewableItems.length >= 1) {
         const visibleFileIndex = viewableItems[0].index
        //  console.log("visible index " + visibleFileIndex)
        this.setState({currentVisibleIndex:visibleFileIndex}) 
        const file = this.props.schedulesFiles[visibleFileIndex]
         const fileType = getFileType(file)
         console.log("file type is " + fileType)
         if (fileType == FileType.Video) {
           console.log("video file type")
         } else {
           this.schedulePlayDurationTimer(visibleFileIndex)
         }
       }
    }

    getItemLayout = (data, index) => ({
      length: deviceWidth,
      offset: deviceWidth * index,
      index,
    })

    componentDidMount(){
      this.props.getScheduleFiles()  
    }

    shouldComponentUpdate(nextProps, nextState) {
      return true
    }

    componentDidUpdate(){
      console.log("componentDidUpdate")
    }


    schedulePlayDurationTimer(file_index) {
      const file = this.props.schedulesFiles[file_index]
      const playDuration = file.play_duration_in_milliseconds
      this.timer = setTimeout(() => {
        clearTimeout(this.timer)
        this.playNextFile()
      }, playDuration);
    }


   videoEnded = () => {
     console.log("video ended")
     this.playNextFile()
   }

    playNextFile = () => {
      if(this.props.currentlyPlayingIndex == (this.props.schedulesFiles.length - 1)) {
        //last file played
        this.props.getScheduleFiles() 
        this.props.playNextFile(this.props.schedulesFiles,this.props.currentlyPlayingIndex)
        this.listRef.scrollToIndex({animated: false, index: this.props.currentlyPlayingIndex})

      } else {
        console.log("playNextFile current index " + this.props.currentlyPlayingIndex)
        this.props.playNextFile(this.props.schedulesFiles,this.props.currentlyPlayingIndex)
        console.log("playNextFile next index " + this.props.currentlyPlayingIndex)
        this.listRef.scrollToIndex({animated: true, index: this.props.currentlyPlayingIndex})
      }
    }

    _renderItem = ({item, index}) => {
      return (
        this.renderPlayer(item,index)
      );
    }

    renderPlayer(file,index) {    
        switch (getFileType(file)) {
          case FileType.Video:
            return <VideoPlayer file={file}  onEnd={this.videoEnded} currentIndex={index} currentVisibleIndex={this.state.currentVisibleIndex} />
            case FileType.Audio:
              return <AudioPlayer file={file} onEnd={this.playNextFile} />
          case FileType.Image:
            return <ImagePlayer file={file} onEnd={this.playNextFile} />
          case FileType.Pdf:
              return <PdfPlayer file={file} onEnd={this.playNextFile} />
          case FileType.WebpageContent:
            return <WebsitePlayer file={file} onEnd={this.playNextFile} />
          default:
            showToastMessage(
              I18n.t('ErrorMessage.FormatNotSupported', {
                name: getFileExtension(file).toUpperCase()
              })
            )
            this.playNextFile()
        }
    }

    render() {
      if(this.props.schedulesFiles.length > 0 ) {
          return (

              <View style={{flex:1}}>
              <StatusBar hidden={true} />
              <FlatList
                style={{flex:1}}
                bounces={false}
                removeClippedSubviews={true}
                scrollEnabled={false}
                showsHorizontalScrollIndicator={false}

                ref={el => this.listRef = el}
                horizontal={true}

                keyExtractor={(item, index) => index.toString()}

                data={this.props.schedulesFiles}
                renderItem={this._renderItem}
                onViewableItemsChanged={this.onViewableItemsChanged}
                viewabilityConfig={this.viewabilityConfig}


                getItemLayout={this.getItemLayout}
                initialNumToRender={2}
                maxToRenderPerBatch={2}
                windowSize={this.props.schedulesFiles.length}
              />
            <KeepAwake />
            </View>
          )
      }else {
        return (
          <TouchableWithoutFeedback delayLongPress={constants.REVEAL_SIDE_BAR_MENU_PRESS_DURATION} onLongPress={() => this.props.navigation.openDrawer()}>
            <View style={styles.container}>
                <NothingTpPlay/>
                <KeepAwake />
            </View>
          </TouchableWithoutFeedback>
        )
      }
    }
}


const styles = ScaledSheet.create({
    container: {
      flex:1,
      backgroundColor : 'white', 
    }
  });


//redux binding
// The function takes data from the app current state,
// and insert/links it into the props of our component.
// This function makes Redux know that this component needs to be passed a piece of the state
function mapStateToProps(state, props) {
  return {
      loading: state.scheduleReducer.loading,
      schedulesFiles: state.scheduleReducer.data,
      currentlyPlayingIndex: state.scheduleReducer.nextFileIndex,
  }
}

// Doing this merges our actions into the component’s props,
// while wrapping them in dispatch() so that they immediately dispatch an Action.
// Just by doing this, we will have access to the actions defined in out actions file (action/home.js)
function mapDispatchToProps(dispatch) {
  return bindActionCreators(Actions, dispatch);
}

//Connect everything
export default connect(mapStateToProps, mapDispatchToProps)(PlaylistPlayerScreen);

Render method for video player child component code is:

<VideoPlayer file={file}  onEnd={this.videoEnded} currentIndex={index} currentVisibleIndex={this.state.currentVisibleIndex} />

VideoPlayer.js relevant code

export default class VideoPlayer extends React.Component {

  constructor() {
    super();
    this.state = {
    }
    this.videoEnded = this.videoEnded.bind(this)
  }

 videoEnded() {
    if (this.props.shouldRepeat == true) {
    } else {
      this.video.paused = true
      this.video.seek(0)
    }
    this.props.onEnd()
  }

render() {
    return (
      <Video 
        ref={ref => {
            this.video = ref;
          }}
        onError={this.videoEnded}
        minLoadRetryCount={1}
        useTextureView={true}
        controls={false} 
        style={ContainerStyle.playerTypeStyle} 
        onEnd={this.videoEnded}
        repeat={this.props.shouldRepeat} 
        playInBackground={false}
        playWhenInactive={false}
        ignoreSilentSwitch={"ignore"}

        resizeMode={this.props.file.resize_mode} 
        source={{uri:getFileAbsolutePath(this.props.file)}} 
        paused={this.props.currentIndex != this.props.currentVisibleIndex}
        />      
    )
  }

}

Reducer code

import { combineReducers } from 'redux';

import { SCHEDULE_REFRESHED,PLAY_NEXT_FILE } from "../actions/ScheduleActions.js" //Import the actions types constant we defined in our actions

let dataState = { data: [], loading:true };

const scheduleReducer = (state = dataState, action) => {
    switch (action.type) {
        case SCHEDULE_REFRESHED:
            state = Object.assign({}, state, { data: action.data, nextFileIndex:action.nextFileIndex });
            return state;
        case PLAY_NEXT_FILE:
            state = Object.assign({}, state, { nextFileIndex: action.nextFileIndex});
            return state;
        default:
            return state;
    }
}

// Combine all the reducers
const rootReducer = combineReducers({
    scheduleReducer
    // ,[ANOTHER REDUCER], [ANOTHER REDUCER] ....
})

export default rootReducer;

Action code

//gets called initially on app launch to get files to be played
export function getScheduleFiles(){
    return (dispatch) => {
        getOfflineNextScheduleFiles().then((files)=>{//get offline files/schedule first
            plainFiles = convertToArray(files)
            dispatch({type: SCHEDULE_REFRESHED, data:plainFiles,nextFileIndex:0});
        }).catch((error)=>{//if offline schedules is not available to play, refresh online
            triggerPlaylistsRefresh().then((files)=>{
                plainFiles = convertToArray(files)
                dispatch({type: SCHEDULE_REFRESHED, data:plainFiles,nextFileIndex:0});
            }).catch((error)=>{
                console.log("nothing to play")
                dispatch({type: PLAY_NEXT_FILE, nextFileIndex:0});
                showToastMessage(I18n.t("ErrorMessage.NoSchedulesAvailableForCurrentTimeError"))
            })
        })
    }
}

//get called from PlaylistPlayerScreen after each file played
export function playNextFile(files,filePlayedIndex){
    return (dispatch) => {
        if(filePlayedIndex < files.length-1) {
            dispatch({type: PLAY_NEXT_FILE, nextFileIndex:filePlayedIndex+1});
          }else {
            console.log("all files played")
            dispatch({type: PLAY_NEXT_FILE, nextFileIndex:0});
          }
    }
}
CMS Signage
  • 2,755
  • 1
  • 26
  • 50
  • 1
    I would really like to see what happens inside of your reducer and in the parent component. Can you drop us some more code? – Murmeltier Apr 17 '20 at 17:06
  • 1
    Thanks @Murmeltier, I have added some more code – CMS Signage Apr 17 '20 at 17:12
  • 1
    And it calls the video ended method but screws up somewhere at the reducer/redux/wizardry, is that right? – Murmeltier Apr 17 '20 at 17:19
  • yes, this.props.playNextFile(this.props.schedulesFiles,this.props.currentlyPlayingIndex), value of currentlyPlayingIndex remains same instead it should increment by one – CMS Signage Apr 17 '20 at 17:41
  • 1
    if same method i.e playNextFile() call through timer fired/ended in the parent component, it works like a charm – CMS Signage Apr 17 '20 at 17:44

0 Answers0