1

I managed to build a simple music player using react-native-track-player following a tutorial as part of my learning curve in react native. Now, instead of just playing/streaming songs from provided url tracks in my array of songs, I want to stream the songs from API (though it can beb any API - but I have registered for Napster API) which I feel is not limited by just few songs. But, I can't put together how to implement or call the Napster API to fetch songs. Please any help/guide I will appreciate so much.

Below is my code:

I have data.js, my array of songs:

const songs = [
  {
    title: "death bed",
    artist: "Powfu",
    artwork: require("../assets/album-arts/death-bed.jpg"),
    url: "https://github.com/ShivamJoker/sample-songs/raw/master/death%20bed.mp3",
    id: "1",
  },
  {
    title: "bad liar",
    artist: "Imagine Dragons",
    artwork: require("../assets/album-arts/bad-liar.jpg"),
    url: "https://github.com/ShivamJoker/sample-songs/raw/master/Bad%20Liar.mp3",
    id: "2",
  },
  {
    title: "faded",
    artist: "Alan Walker",
    artwork: require("../assets/album-arts/faded.jpg"),
    url: "https://github.com/ShivamJoker/sample-songs/raw/master/Faded.mp3",
    id: "3",
  },
];

export default songs;

And here's my playerScreen.js :

import React, {useRef, useEffect, useState} from 'react';
import {
  View,
  SafeAreaView,
  Text,
  Image,
  FlatList,
  Dimensions,
  Animated,
  StyleSheet,
} from 'react-native';

import TrackPlayer, {
  Capability,
  useTrackPlayerEvents,
  usePlaybackState,
  TrackPlayerEvents,
  STATE_PLAYING,
  Event,
} from 'react-native-track-player';

import songs from './data';
import Controller from './Controller';
import SliderComp from './SliderComp';

const {width, height} = Dimensions.get('window');

// const events = [
//   TrackPlayerEvents.PLAYBACK_STATE,
//   TrackPlayerEvents.PLAYBACK_ERROR
// ];

export default function PlayerScreen() {
  const scrollX = useRef(new Animated.Value(0)).current;

  const slider = useRef(null);
  const isPlayerReady = useRef(false);
  const index = useRef(0);

  const [songIndex, setSongIndex] = useState(0);

  const isItFromUser = useRef(true);

  // for tranlating the album art
  const position = useRef(Animated.divide(scrollX, width)).current;
  const playbackState = usePlaybackState();

  useEffect(() => {
    // position.addListener(({ value }) => {
    //   console.log(value);
    // });

    scrollX.addListener(({value}) => {
      const val = Math.round(value / width);

      setSongIndex(val);
    });

    TrackPlayer.setupPlayer().then(async () => {
      // The player is ready to be used
      console.log('Player ready');
      // add the array of songs in the playlist
      await TrackPlayer.reset();
      await TrackPlayer.add(songs);
      TrackPlayer.play();
      isPlayerReady.current = true;

      await TrackPlayer.updateOptions({
        stopWithApp: false,
        alwaysPauseOnInterruption: true,
        capabilities: [
          Capability.Play,
          Capability.Pause,
          Capability.SkipToNext,
          Capability.SkipToPrevious,
        ],
      });
      //add listener on track change
      TrackPlayer.addEventListener(Event.PlaybackTrackChanged, async (e) => {
        console.log('song ended', e);

        const trackId = (await TrackPlayer.getCurrentTrack()) - 1; //get the current id

        console.log('track id', trackId, 'index', index.current);

        if (trackId !== index.current) {
          setSongIndex(trackId);
          isItFromUser.current = false;

          if (trackId > index.current) {
            goNext();
          } else {
            goPrv();
          }
          setTimeout(() => {
            isItFromUser.current = true;
          }, 200);
        }

        // isPlayerReady.current = true;
      });

      //monitor intterupt when other apps start playing music
      TrackPlayer.addEventListener(Event.RemoteDuck, (e) => {
        // console.log(e);
        if (e.paused) {
          // if pause true we need to pause the music
          TrackPlayer.pause();
        } else {
          TrackPlayer.play();
        }
      });
    });

    return () => {
      scrollX.removeAllListeners();
      TrackPlayer.destroy();

      // exitPlayer();
    };
  }, []);

  // change the song when index changes
  useEffect(() => {
    if (isPlayerReady.current && isItFromUser.current) {
      TrackPlayer.skip(songs[songIndex].id)
        .then((_) => {
          console.log('changed track');
        })
        .catch((e) => console.log('error in changing track ', e));
    }
    index.current = songIndex;
  }, [songIndex]);

  const exitPlayer = async () => {
    try {
      await TrackPlayer.stop();
    } catch (error) {
      console.error('exitPlayer', error);
    }
  };

  const goNext = async () => {
    slider.current.scrollToOffset({
      offset: (index.current + 1) * width,
    });

    await TrackPlayer.play();
  };
  const goPrv = async () => {
    slider.current.scrollToOffset({
      offset: (index.current - 1) * width,
    });

    await TrackPlayer.play();
  };

  const renderItem = ({index, item}) => {
    return (
      <Animated.View
        style={{
          alignItems: 'center',
          width: width,
          transform: [
            {
              translateX: Animated.multiply(
                Animated.add(position, -index),
                -100,
              ),
            },
          ],
        }}>
        <Animated.Image
          source={item.artwork}
          style={{width: 320, height: 320, borderRadius: 5}}
        />
      </Animated.View>
    );
  };

  return (
    <SafeAreaView style={styles.container}>
      <SafeAreaView style={{height: 320}}>
        <Animated.FlatList
          ref={slider}
          horizontal
          pagingEnabled
          showsHorizontalScrollIndicator={false}
          scrollEventThrottle={16}
          data={songs}
          renderItem={renderItem}
          keyExtractor={(item) => item.id}
          onScroll={Animated.event(
            [{nativeEvent: {contentOffset: {x: scrollX}}}],
            {useNativeDriver: true},
          )}
        />
      </SafeAreaView>
      <View>
        <Text style={styles.title}>{songs[songIndex].title}</Text>
        <Text style={styles.artist}>{songs[songIndex].artist}</Text>
      </View>

      <SliderComp />

      <Controller onNext={goNext} onPrv={goPrv} />
    </SafeAreaView>
  );
}
Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
Benjamin Ikwuagwu
  • 377
  • 1
  • 9
  • 28

1 Answers1

0

I would pass the songs into your current PlayerScreen through props. You can use a separate component to load tracks from the Napster API and then render a PlayerScreen with those props.

The only part I'm not sure about is what path to pass to the player as the url. The Napster data contains a property previewURL which is an mp3 but it's not the whole song. I believe that the href is the streamable URL. It requires authentication to load the full track though.

The API path that I'm using here is for the most popular tracks.

export default function TopTracks() {
  const [songs, setSongs] = useState([]);

  useEffect(() => {
    const loadData = async () => {
      try {
        const url = `http://api.napster.com/v2.2/tracks/top?apikey=${API_KEY}&limit=5`;
        const res = await axios.get(url);
        setSongs(
          res.data.tracks.map((track) => ({
            duration: track.playbackSeconds,
            title: track.name,
            artist: track.artistName,
            album: track.albumName,
            id: track.id,
            url: track.href // or track.previewURL?
          }))
        );
      } catch (error) {
        console.error(error);
      }
    };

    loadData();
  }, []);

  return <PlayerScreen songs={songs} />;
}
Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
  • Thank you so much for your response. I don't seem to understand what you mean by "I would pass the songs into your current PlayerScreen through props." because the file data.js that has the songs array still exist. – Benjamin Ikwuagwu Mar 31 '21 at 12:21