Documentation

Tutorials: Station playback

Playback component

Now that we have 2 ways to work with the data (via the mutation or the component), we can attach this to as many components as we want.  

In the Sample Web App we have the Playback component that handles all the playback functions.

The playback component is displayed across the bottom of the screen.  It makes use of an AudioPlayer component to display the playback icons and track info.



...

import AudioPlayer from '../../components/AudioPlayer';
import { withCurrentlyPlaying } from '../../graphql/playback/currentlyPlaying';
import { withSetPlaybackSource } from '../../graphql/playback/setPlaybackSource';
import { withSetEnded } from '../../graphql/playback/setEnded';
import { withPause } from '../../graphql/playback/pause';
import { withSetStarted } from '../../graphql/playback/setStarted';
import { withSetProgress } from '../../graphql/playback/setProgress';
import { withThumbUp, withThumbDown, withRemoveFeedback } from '../../graphql/playback/feedback';
import { withSkip } from '../../graphql/playback/skip';

...

let playedTime = 0;
let activePlaybackId = undefined;

const Playback = (props) => {
 const {
   deviceUuid,
   sourceIdentification,
   errorMessage,
   isLoading,
   playbackItem,
   progressInterval,
   onNewSource,
   onPause,
   onPlay,
   onProgressUpdated,
   onEnd,
   onSkip,
   onThumbDown,
   onThumbUp,
   onRemoveThumb
 } = props;
  
 if (isLoading) { return null; }

 if (errorMessage && !playbackItem) {
   return ;
 }

 let onTimeUpdated = (timeInSeconds) => {
   if (timeInSeconds % progressInterval === 0 && timeInSeconds !== 0) {
     onProgressUpdated(deviceUuid, sourceId, index, timeInSeconds);
   }
   playedTime = timeInSeconds;
 }

  const sourceId = playbackItem ? playbackItem.sourceId : undefined;

 if (sourceIdentification && sourceId !== sourceIdentification && ((!activePlaybackId && !sourceId) || !activePlaybackId || (sourceIdentification !== activePlaybackId))) {
   onNewSource(deviceUuid, sourceIdentification);
   activePlaybackId = sourceIdentification
 }

 if (!playbackItem) return null;

 const {
   audioUrl,
   url,
   artistName,
   trackTitle,
   duration,
   index,
   trackId,
   trackToken,
   interactions,
   feedbackValue,
 } = playbackItem;

  const showSkip = interactions && interactions.includes(Interactions.SKIP);
 const showThumbs = interactions && interactions.includes(Interactions.THUMB);
 const isThumbsUpActive = feedbackValue === FeedbackValue.UP;
 const isThumbsDownActive = feedbackValue === FeedbackValue.DOWN;

 return (
   <>
     { errorMessage &&  }
      { onPause(deviceUuid, sourceId, index, playedTime); }}
       onPlay={() => { onPlay(deviceUuid, sourceId, index, playedTime); }}
       onEnded={() => { onEnd(deviceUuid, sourceId, index, duration || playedTime); }}
       onSkip={() => { onSkip(deviceUuid, sourceId, index, playedTime); }}
       onThumbUpToggle={(active) => { active ? onThumbUp(deviceUuid, sourceId, trackId, trackToken, playedTime) : onRemoveThumb(deviceUuid, sourceId, trackId, trackToken, playedTime); }}
       onThumbDownToggle={(active) => { active ? onThumbDown(deviceUuid, sourceId, trackId, trackToken, playedTime)  : onRemoveThumb(deviceUuid, sourceId, trackId, trackToken, playedTime); }}
     />
   
 );
};

Playback.propTypes = {
 errorMessage: PropTypes.string,
 isLoading: PropTypes.bool,
 sourceIdentification: PropTypes.string,
 progressInterval: PropTypes.number,
 onSkip: PropTypes.func,
 onPause: PropTypes.func,
 onPlay: PropTypes.func,
 onEnd: PropTypes.func,
 onThumbUp: PropTypes.func,
 onThumbDown: PropTypes.func,
 onRemoveThumb: PropTypes.func,
 onProgressUpdated: PropTypes.func,
 onNewSource: PropTypes.func,
 hasInactiveStream: PropTypes.bool,
 onInactiveStream: PropTypes.func,
};

Playback.defaultProps = {
 isLoading: false,
 hasInactiveStream: false,
 progressInterval: 50,
 onSkip: () => {},
 onPause: () => {},
 onPlay: () => {},
 onEnd: () => {},
 onThumbUp: () => {},
 onThumbDown: () => {},
 onRemoveThumb: () => {},
 onProgressUpdated: () => {},
 onNewSource: () => {},
 onInactiveStream: () => {},
};

const mapStateToProps = ({ authentication }) => ({
 deviceUuid: authentication.deviceUuid,
});

export default connect(mapStateToProps)(memo(Playback, areEqual));

export const PlaybackWithGraphQl = compose(
   connect(mapStateToProps),
   withCurrentlyPlaying,
   withSetPlaybackSource,
   withSkip,
   withPause,
   withThumbUp,
   withThumbDown,
   withRemoveFeedback,
   withSetStarted,
   withSetProgress,
   withSetEnded
 )(memo(Playback, areEqual));

The first block of code shows all the playback mutations and queries that is used by the Playback component.  The AudioPlayer component is also imported.

The onTimeUpdated function controls the setProgress mutation of playback.  It is set on the onTimeUpdated property of the AudioPlayer component.  The progressInterval property is also used here to manage the rate at which setProgress should be sent.

A sourceIdentification property is available on the Playback component. When this is set the onNewSource property’s function will be called. This is the setPlaybackSource mutation.  When the source mutation completes, it updates the cache and fetches the current playback item using the playback current query.  This will re-render the Playback component and contain the playbackItem property will contain the values of the current playback item.

If the interactions property contains SKIP as a value, it will show the skip button.  The same is done for the thumb buttons. Read more about the interaction values here.

The thumb buttons are also active when the feedbackValue property contains a value of UP or DOWN.  Read more about feedback here.

Lastly, the AudioPlayer is displayed with all the values of the playbackItem property.

The last block of code is where the mutations and query is bound to the Playback component.