[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\"babel-preset-expo\"]\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules/**/*\n.expo/*\nnpm-debug.*\n"
  },
  {
    "path": "App.js",
    "content": "/**\n * @flow\n */\n\nimport React from \"react\";\nimport {\n  Dimensions,\n  Image,\n  StyleSheet,\n  Text,\n  TouchableHighlight,\n  View\n} from \"react-native\";\nimport { Asset } from \"expo-asset\";\nimport {\n  Audio,\n  InterruptionModeAndroid,\n  InterruptionModeIOS,\n  ResizeMode,\n  Video\n} from \"expo-av\";\nimport * as Font from \"expo-font\";\nimport Slider from \"@react-native-community/slider\";\n\nimport { MaterialIcons } from \"@expo/vector-icons\";\n\nclass Icon {\n  constructor(module, width, height) {\n    this.module = module;\n    this.width = width;\n    this.height = height;\n    Asset.fromModule(this.module).downloadAsync();\n  }\n}\n\nclass PlaylistItem {\n  constructor(name, uri, isVideo) {\n    this.name = name;\n    this.uri = uri;\n    this.isVideo = isVideo;\n  }\n}\n\nconst PLAYLIST = [\n  new PlaylistItem(\n    \"Comfort Fit - “Sorry”\",\n    \"https://s3.amazonaws.com/exp-us-standard/audio/playlist-example/Comfort_Fit_-_03_-_Sorry.mp3\",\n    false\n  ),\n  new PlaylistItem(\n    \"Big Buck Bunny\",\n    \"http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4\",\n    true\n  ),\n  new PlaylistItem(\n    \"Mildred Bailey – “All Of Me”\",\n    \"https://ia800304.us.archive.org/34/items/PaulWhitemanwithMildredBailey/PaulWhitemanwithMildredBailey-AllofMe.mp3\",\n    false\n  ),\n  new PlaylistItem(\n    \"Popeye - I don't scare\",\n    \"https://ia800501.us.archive.org/11/items/popeye_i_dont_scare/popeye_i_dont_scare_512kb.mp4\",\n    true\n  ),\n  new PlaylistItem(\n    \"Podington Bear - “Rubber Robot”\",\n    \"https://s3.amazonaws.com/exp-us-standard/audio/playlist-example/Podington_Bear_-_Rubber_Robot.mp3\",\n    false\n  )\n];\n\nconst ICON_THROUGH_EARPIECE = \"speaker-phone\";\nconst ICON_THROUGH_SPEAKER = \"speaker\";\n\nconst ICON_PLAY_BUTTON = new Icon(\n  require(\"./assets/images/play_button.png\"),\n  34,\n  51\n);\nconst ICON_PAUSE_BUTTON = new Icon(\n  require(\"./assets/images/pause_button.png\"),\n  34,\n  51\n);\nconst ICON_STOP_BUTTON = new Icon(\n  require(\"./assets/images/stop_button.png\"),\n  22,\n  22\n);\nconst ICON_FORWARD_BUTTON = new Icon(\n  require(\"./assets/images/forward_button.png\"),\n  33,\n  25\n);\nconst ICON_BACK_BUTTON = new Icon(\n  require(\"./assets/images/back_button.png\"),\n  33,\n  25\n);\n\nconst ICON_LOOP_ALL_BUTTON = new Icon(\n  require(\"./assets/images/loop_all_button.png\"),\n  77,\n  35\n);\nconst ICON_LOOP_ONE_BUTTON = new Icon(\n  require(\"./assets/images/loop_one_button.png\"),\n  77,\n  35\n);\n\nconst ICON_MUTED_BUTTON = new Icon(\n  require(\"./assets/images/muted_button.png\"),\n  67,\n  58\n);\nconst ICON_UNMUTED_BUTTON = new Icon(\n  require(\"./assets/images/unmuted_button.png\"),\n  67,\n  58\n);\n\nconst ICON_TRACK_1 = new Icon(require(\"./assets/images/track_1.png\"), 166, 5);\nconst ICON_THUMB_1 = new Icon(require(\"./assets/images/thumb_1.png\"), 18, 19);\nconst ICON_THUMB_2 = new Icon(require(\"./assets/images/thumb_2.png\"), 15, 19);\n\nconst LOOPING_TYPE_ALL = 0;\nconst LOOPING_TYPE_ONE = 1;\nconst LOOPING_TYPE_ICONS = { 0: ICON_LOOP_ALL_BUTTON, 1: ICON_LOOP_ONE_BUTTON };\n\nconst { width: DEVICE_WIDTH, height: DEVICE_HEIGHT } = Dimensions.get(\"window\");\nconst BACKGROUND_COLOR = \"#FFF8ED\";\nconst DISABLED_OPACITY = 0.5;\nconst FONT_SIZE = 14;\nconst LOADING_STRING = \"... loading ...\";\nconst BUFFERING_STRING = \"...buffering...\";\nconst RATE_SCALE = 3.0;\nconst VIDEO_CONTAINER_HEIGHT = (DEVICE_HEIGHT * 2.0) / 5.0 - FONT_SIZE * 2;\n\nexport default class App extends React.Component {\n  constructor(props) {\n    super(props);\n    this.index = 0;\n    this.isSeeking = false;\n    this.shouldPlayAtEndOfSeek = false;\n    this.playbackInstance = null;\n    this.state = {\n      showVideo: false,\n      playbackInstanceName: LOADING_STRING,\n      loopingType: LOOPING_TYPE_ALL,\n      muted: false,\n      playbackInstancePosition: null,\n      playbackInstanceDuration: null,\n      shouldPlay: false,\n      isPlaying: false,\n      isBuffering: false,\n      isLoading: true,\n      fontLoaded: false,\n      shouldCorrectPitch: true,\n      volume: 1.0,\n      rate: 1.0,\n      videoWidth: DEVICE_WIDTH,\n      videoHeight: VIDEO_CONTAINER_HEIGHT,\n      poster: false,\n      useNativeControls: false,\n      fullscreen: false,\n      throughEarpiece: false\n    };\n  }\n\n  componentDidMount() {\n    Audio.setAudioModeAsync({\n      allowsRecordingIOS: false,\n      staysActiveInBackground: false,\n      interruptionModeIOS: InterruptionModeIOS.DoNotMix,\n      playsInSilentModeIOS: true,\n      shouldDuckAndroid: true,\n      interruptionModeAndroid: InterruptionModeAndroid.DoNotMix,\n      playThroughEarpieceAndroid: false\n    });\n    (async () => {\n      await Font.loadAsync({\n        ...MaterialIcons.font,\n        \"cutive-mono-regular\": require(\"./assets/fonts/CutiveMono-Regular.ttf\")\n      });\n      this.setState({ fontLoaded: true });\n    })();\n  }\n\n  async _loadNewPlaybackInstance(playing) {\n    if (this.playbackInstance != null) {\n      await this.playbackInstance.unloadAsync();\n      // this.playbackInstance.setOnPlaybackStatusUpdate(null);\n      this.playbackInstance = null;\n    }\n\n    const source = { uri: PLAYLIST[this.index].uri };\n    const initialStatus = {\n      shouldPlay: playing,\n      rate: this.state.rate,\n      shouldCorrectPitch: this.state.shouldCorrectPitch,\n      volume: this.state.volume,\n      isMuted: this.state.muted,\n      isLooping: this.state.loopingType === LOOPING_TYPE_ONE\n      // // UNCOMMENT THIS TO TEST THE OLD androidImplementation:\n      // androidImplementation: 'MediaPlayer',\n    };\n\n    if (PLAYLIST[this.index].isVideo) {\n      await this._video.loadAsync(source, initialStatus);\n      // this._video.onPlaybackStatusUpdate(this._onPlaybackStatusUpdate);\n      this.playbackInstance = this._video;\n      const status = await this._video.getStatusAsync();\n    } else {\n      const { sound, status } = await Audio.Sound.createAsync(\n        source,\n        initialStatus,\n        this._onPlaybackStatusUpdate\n      );\n      this.playbackInstance = sound;\n    }\n\n    this._updateScreenForLoading(false);\n  }\n\n  _mountVideo = component => {\n    this._video = component;\n    this._loadNewPlaybackInstance(false);\n  };\n\n  _updateScreenForLoading(isLoading) {\n    if (isLoading) {\n      this.setState({\n        showVideo: false,\n        isPlaying: false,\n        playbackInstanceName: LOADING_STRING,\n        playbackInstanceDuration: null,\n        playbackInstancePosition: null,\n        isLoading: true\n      });\n    } else {\n      this.setState({\n        playbackInstanceName: PLAYLIST[this.index].name,\n        showVideo: PLAYLIST[this.index].isVideo,\n        isLoading: false\n      });\n    }\n  }\n\n  _onPlaybackStatusUpdate = status => {\n    if (status.isLoaded) {\n      this.setState({\n        playbackInstancePosition: status.positionMillis,\n        playbackInstanceDuration: status.durationMillis,\n        shouldPlay: status.shouldPlay,\n        isPlaying: status.isPlaying,\n        isBuffering: status.isBuffering,\n        rate: status.rate,\n        muted: status.isMuted,\n        volume: status.volume,\n        loopingType: status.isLooping ? LOOPING_TYPE_ONE : LOOPING_TYPE_ALL,\n        shouldCorrectPitch: status.shouldCorrectPitch\n      });\n      if (status.didJustFinish && !status.isLooping) {\n        this._advanceIndex(true);\n        this._updatePlaybackInstanceForIndex(true);\n      }\n    } else {\n      if (status.error) {\n        console.log(`FATAL PLAYER ERROR: ${status.error}`);\n      }\n    }\n  };\n\n  _onLoadStart = () => {\n    console.log(`ON LOAD START`);\n  };\n\n  _onLoad = status => {\n    console.log(`ON LOAD : ${JSON.stringify(status)}`);\n  };\n\n  _onError = error => {\n    console.log(`ON ERROR : ${error}`);\n  };\n\n  _onReadyForDisplay = event => {\n    const widestHeight =\n      (DEVICE_WIDTH * event.naturalSize.height) / event.naturalSize.width;\n    if (widestHeight > VIDEO_CONTAINER_HEIGHT) {\n      this.setState({\n        videoWidth:\n          (VIDEO_CONTAINER_HEIGHT * event.naturalSize.width) /\n          event.naturalSize.height,\n        videoHeight: VIDEO_CONTAINER_HEIGHT\n      });\n    } else {\n      this.setState({\n        videoWidth: DEVICE_WIDTH,\n        videoHeight:\n          (DEVICE_WIDTH * event.naturalSize.height) / event.naturalSize.width\n      });\n    }\n  };\n\n  _onFullscreenUpdate = event => {\n    console.log(\n      `FULLSCREEN UPDATE : ${JSON.stringify(event.fullscreenUpdate)}`\n    );\n  };\n\n  _advanceIndex(forward) {\n    this.index =\n      (this.index + (forward ? 1 : PLAYLIST.length - 1)) % PLAYLIST.length;\n  }\n\n  async _updatePlaybackInstanceForIndex(playing) {\n    this._updateScreenForLoading(true);\n\n    this.setState({\n      videoWidth: DEVICE_WIDTH,\n      videoHeight: VIDEO_CONTAINER_HEIGHT\n    });\n\n    this._loadNewPlaybackInstance(playing);\n  }\n\n  _onPlayPausePressed = () => {\n    if (this.playbackInstance != null) {\n      if (this.state.isPlaying) {\n        this.playbackInstance.pauseAsync();\n      } else {\n        this.playbackInstance.playAsync();\n      }\n    }\n  };\n\n  _onStopPressed = () => {\n    if (this.playbackInstance != null) {\n      this.playbackInstance.stopAsync();\n    }\n  };\n\n  _onForwardPressed = () => {\n    if (this.playbackInstance != null) {\n      this._advanceIndex(true);\n      this._updatePlaybackInstanceForIndex(this.state.shouldPlay);\n    }\n  };\n\n  _onBackPressed = () => {\n    if (this.playbackInstance != null) {\n      this._advanceIndex(false);\n      this._updatePlaybackInstanceForIndex(this.state.shouldPlay);\n    }\n  };\n\n  _onMutePressed = () => {\n    if (this.playbackInstance != null) {\n      this.playbackInstance.setIsMutedAsync(!this.state.muted);\n    }\n  };\n\n  _onLoopPressed = () => {\n    if (this.playbackInstance != null) {\n      this.playbackInstance.setIsLoopingAsync(\n        this.state.loopingType !== LOOPING_TYPE_ONE\n      );\n    }\n  };\n\n  _onVolumeSliderValueChange = value => {\n    if (this.playbackInstance != null) {\n      this.playbackInstance.setVolumeAsync(value);\n    }\n  };\n\n  _trySetRate = async (rate, shouldCorrectPitch) => {\n    if (this.playbackInstance != null) {\n      try {\n        await this.playbackInstance.setRateAsync(rate, shouldCorrectPitch);\n      } catch (error) {\n        // Rate changing could not be performed, possibly because the client's Android API is too old.\n      }\n    }\n  };\n\n  _onRateSliderSlidingComplete = async value => {\n    this._trySetRate(value * RATE_SCALE, this.state.shouldCorrectPitch);\n  };\n\n  _onPitchCorrectionPressed = async value => {\n    this._trySetRate(this.state.rate, !this.state.shouldCorrectPitch);\n  };\n\n  _onSeekSliderValueChange = value => {\n    if (this.playbackInstance != null && !this.isSeeking) {\n      this.isSeeking = true;\n      this.shouldPlayAtEndOfSeek = this.state.shouldPlay;\n      this.playbackInstance.pauseAsync();\n    }\n  };\n\n  _onSeekSliderSlidingComplete = async value => {\n    if (this.playbackInstance != null) {\n      this.isSeeking = false;\n      const seekPosition = value * this.state.playbackInstanceDuration;\n      if (this.shouldPlayAtEndOfSeek) {\n        this.playbackInstance.playFromPositionAsync(seekPosition);\n      } else {\n        this.playbackInstance.setPositionAsync(seekPosition);\n      }\n    }\n  };\n\n  _getSeekSliderPosition() {\n    if (\n      this.playbackInstance != null &&\n      this.state.playbackInstancePosition != null &&\n      this.state.playbackInstanceDuration != null\n    ) {\n      return (\n        this.state.playbackInstancePosition /\n        this.state.playbackInstanceDuration\n      );\n    }\n    return 0;\n  }\n\n  _getMMSSFromMillis(millis) {\n    const totalSeconds = millis / 1000;\n    const seconds = Math.floor(totalSeconds % 60);\n    const minutes = Math.floor(totalSeconds / 60);\n\n    const padWithZero = number => {\n      const string = number.toString();\n      if (number < 10) {\n        return \"0\" + string;\n      }\n      return string;\n    };\n    return padWithZero(minutes) + \":\" + padWithZero(seconds);\n  }\n\n  _getTimestamp() {\n    if (\n      this.playbackInstance != null &&\n      this.state.playbackInstancePosition != null &&\n      this.state.playbackInstanceDuration != null\n    ) {\n      return `${this._getMMSSFromMillis(\n        this.state.playbackInstancePosition\n      )} / ${this._getMMSSFromMillis(this.state.playbackInstanceDuration)}`;\n    }\n    return \"\";\n  }\n\n  _onPosterPressed = () => {\n    this.setState({ poster: !this.state.poster });\n  };\n\n  _onUseNativeControlsPressed = () => {\n    this.setState({ useNativeControls: !this.state.useNativeControls });\n  };\n\n  _onFullscreenPressed = () => {\n    try {\n      this._video.presentFullscreenPlayer();\n    } catch (error) {\n      console.log(error.toString());\n    }\n  };\n\n  _onSpeakerPressed = () => {\n    this.setState(\n      state => {\n        return { throughEarpiece: !state.throughEarpiece };\n      },\n      () =>\n        Audio.setAudioModeAsync({\n          allowsRecordingIOS: false,\n          interruptionModeIOS: InterruptionModeIOS.DoNotMix,\n          playsInSilentModeIOS: true,\n          shouldDuckAndroid: true,\n          interruptionModeAndroid: InterruptionModeAndroid.DoNotMix,\n          playThroughEarpieceAndroid: this.state.throughEarpiece\n        })\n    );\n  };\n\n  render() {\n    return !this.state.fontLoaded ? (\n      <View style={styles.emptyContainer} />\n    ) : (\n      <View style={styles.container}>\n        <View />\n        <View style={styles.nameContainer}>\n          <Text style={[styles.text, { fontFamily: \"cutive-mono-regular\" }]}>\n            {this.state.playbackInstanceName}\n          </Text>\n        </View>\n        <View style={styles.space} />\n        <View style={styles.videoContainer}>\n          <Video\n            ref={this._mountVideo}\n            style={[\n              styles.video,\n              {\n                opacity: this.state.showVideo ? 1.0 : 0.0,\n                width: this.state.videoWidth,\n                height: this.state.videoHeight\n              }\n            ]}\n            resizeMode={ResizeMode.CONTAIN}\n            onPlaybackStatusUpdate={this._onPlaybackStatusUpdate}\n            onLoadStart={this._onLoadStart}\n            onLoad={this._onLoad}\n            onError={this._onError}\n            onFullscreenUpdate={this._onFullscreenUpdate}\n            onReadyForDisplay={this._onReadyForDisplay}\n            useNativeControls={this.state.useNativeControls}\n          />\n        </View>\n        <View\n          style={[\n            styles.playbackContainer,\n            {\n              opacity: this.state.isLoading ? DISABLED_OPACITY : 1.0\n            }\n          ]}\n        >\n          <Slider\n            style={styles.playbackSlider}\n            trackImage={ICON_TRACK_1.module}\n            thumbImage={ICON_THUMB_1.module}\n            value={this._getSeekSliderPosition()}\n            onValueChange={this._onSeekSliderValueChange}\n            onSlidingComplete={this._onSeekSliderSlidingComplete}\n            disabled={this.state.isLoading}\n          />\n          <View style={styles.timestampRow}>\n            <Text\n              style={[\n                styles.text,\n                styles.buffering,\n                { fontFamily: \"cutive-mono-regular\" }\n              ]}\n            >\n              {this.state.isBuffering ? BUFFERING_STRING : \"\"}\n            </Text>\n            <Text\n              style={[\n                styles.text,\n                styles.timestamp,\n                { fontFamily: \"cutive-mono-regular\" }\n              ]}\n            >\n              {this._getTimestamp()}\n            </Text>\n          </View>\n        </View>\n        <View\n          style={[\n            styles.buttonsContainerBase,\n            styles.buttonsContainerTopRow,\n            {\n              opacity: this.state.isLoading ? DISABLED_OPACITY : 1.0\n            }\n          ]}\n        >\n          <TouchableHighlight\n            underlayColor={BACKGROUND_COLOR}\n            style={styles.wrapper}\n            onPress={this._onBackPressed}\n            disabled={this.state.isLoading}\n          >\n            <Image style={styles.button} source={ICON_BACK_BUTTON.module} />\n          </TouchableHighlight>\n          <TouchableHighlight\n            underlayColor={BACKGROUND_COLOR}\n            style={styles.wrapper}\n            onPress={this._onPlayPausePressed}\n            disabled={this.state.isLoading}\n          >\n            <Image\n              style={styles.button}\n              source={\n                this.state.isPlaying\n                  ? ICON_PAUSE_BUTTON.module\n                  : ICON_PLAY_BUTTON.module\n              }\n            />\n          </TouchableHighlight>\n          <TouchableHighlight\n            underlayColor={BACKGROUND_COLOR}\n            style={styles.wrapper}\n            onPress={this._onStopPressed}\n            disabled={this.state.isLoading}\n          >\n            <Image style={styles.button} source={ICON_STOP_BUTTON.module} />\n          </TouchableHighlight>\n          <TouchableHighlight\n            underlayColor={BACKGROUND_COLOR}\n            style={styles.wrapper}\n            onPress={this._onForwardPressed}\n            disabled={this.state.isLoading}\n          >\n            <Image style={styles.button} source={ICON_FORWARD_BUTTON.module} />\n          </TouchableHighlight>\n        </View>\n        <View\n          style={[\n            styles.buttonsContainerBase,\n            styles.buttonsContainerMiddleRow\n          ]}\n        >\n          <View style={styles.volumeContainer}>\n            <TouchableHighlight\n              underlayColor={BACKGROUND_COLOR}\n              style={styles.wrapper}\n              onPress={this._onMutePressed}\n            >\n              <Image\n                style={styles.button}\n                source={\n                  this.state.muted\n                    ? ICON_MUTED_BUTTON.module\n                    : ICON_UNMUTED_BUTTON.module\n                }\n              />\n            </TouchableHighlight>\n            <Slider\n              style={styles.volumeSlider}\n              trackImage={ICON_TRACK_1.module}\n              thumbImage={ICON_THUMB_2.module}\n              value={1}\n              onValueChange={this._onVolumeSliderValueChange}\n            />\n          </View>\n          <TouchableHighlight\n            underlayColor={BACKGROUND_COLOR}\n            style={styles.wrapper}\n            onPress={this._onLoopPressed}\n          >\n            <Image\n              style={styles.button}\n              source={LOOPING_TYPE_ICONS[this.state.loopingType].module}\n            />\n          </TouchableHighlight>\n        </View>\n        <View\n          style={[\n            styles.buttonsContainerBase,\n            styles.buttonsContainerBottomRow\n          ]}\n        >\n          <TouchableHighlight\n            underlayColor={BACKGROUND_COLOR}\n            style={styles.wrapper}\n            onPress={() => this._trySetRate(1.0, this.state.shouldCorrectPitch)}\n          >\n            <View style={styles.button}>\n              <Text\n                style={[styles.text, { fontFamily: \"cutive-mono-regular\" }]}\n              >\n                Rate:\n              </Text>\n            </View>\n          </TouchableHighlight>\n          <Slider\n            style={styles.rateSlider}\n            trackImage={ICON_TRACK_1.module}\n            thumbImage={ICON_THUMB_1.module}\n            value={this.state.rate / RATE_SCALE}\n            onSlidingComplete={this._onRateSliderSlidingComplete}\n          />\n          <TouchableHighlight\n            underlayColor={BACKGROUND_COLOR}\n            style={styles.wrapper}\n            onPress={this._onPitchCorrectionPressed}\n          >\n            <View style={styles.button}>\n              <Text\n                style={[styles.text, { fontFamily: \"cutive-mono-regular\" }]}\n              >\n                PC: {this.state.shouldCorrectPitch ? \"yes\" : \"no\"}\n              </Text>\n            </View>\n          </TouchableHighlight>\n          <TouchableHighlight\n            onPress={this._onSpeakerPressed}\n            underlayColor={BACKGROUND_COLOR}\n          >\n            <MaterialIcons\n              name={\n                this.state.throughEarpiece\n                  ? ICON_THROUGH_EARPIECE\n                  : ICON_THROUGH_SPEAKER\n              }\n              size={32}\n              color=\"black\"\n            />\n          </TouchableHighlight>\n        </View>\n        <View />\n        {this.state.showVideo ? (\n          <View>\n            <View\n              style={[\n                styles.buttonsContainerBase,\n                styles.buttonsContainerTextRow\n              ]}\n            >\n              <View />\n              <TouchableHighlight\n                underlayColor={BACKGROUND_COLOR}\n                style={styles.wrapper}\n                onPress={this._onPosterPressed}\n              >\n                <View style={styles.button}>\n                  <Text\n                    style={[styles.text, { fontFamily: \"cutive-mono-regular\" }]}\n                  >\n                    Poster: {this.state.poster ? \"yes\" : \"no\"}\n                  </Text>\n                </View>\n              </TouchableHighlight>\n              <View />\n              <TouchableHighlight\n                underlayColor={BACKGROUND_COLOR}\n                style={styles.wrapper}\n                onPress={this._onFullscreenPressed}\n              >\n                <View style={styles.button}>\n                  <Text\n                    style={[styles.text, { fontFamily: \"cutive-mono-regular\" }]}\n                  >\n                    Fullscreen\n                  </Text>\n                </View>\n              </TouchableHighlight>\n              <View />\n            </View>\n            <View style={styles.space} />\n            <View\n              style={[\n                styles.buttonsContainerBase,\n                styles.buttonsContainerTextRow\n              ]}\n            >\n              <View />\n              <TouchableHighlight\n                underlayColor={BACKGROUND_COLOR}\n                style={styles.wrapper}\n                onPress={this._onUseNativeControlsPressed}\n              >\n                <View style={styles.button}>\n                  <Text\n                    style={[styles.text, { fontFamily: \"cutive-mono-regular\" }]}\n                  >\n                    Native Controls:{\" \"}\n                    {this.state.useNativeControls ? \"yes\" : \"no\"}\n                  </Text>\n                </View>\n              </TouchableHighlight>\n              <View />\n            </View>\n          </View>\n        ) : null}\n      </View>\n    );\n  }\n}\n\nconst styles = StyleSheet.create({\n  emptyContainer: {\n    alignSelf: \"stretch\",\n    backgroundColor: BACKGROUND_COLOR\n  },\n  container: {\n    flex: 1,\n    flexDirection: \"column\",\n    justifyContent: \"space-between\",\n    alignItems: \"center\",\n    alignSelf: \"stretch\",\n    backgroundColor: BACKGROUND_COLOR\n  },\n  wrapper: {},\n  nameContainer: {\n    height: FONT_SIZE\n  },\n  space: {\n    height: FONT_SIZE\n  },\n  videoContainer: {\n    height: VIDEO_CONTAINER_HEIGHT\n  },\n  video: {\n    maxWidth: DEVICE_WIDTH\n  },\n  playbackContainer: {\n    flex: 1,\n    flexDirection: \"column\",\n    justifyContent: \"space-between\",\n    alignItems: \"center\",\n    alignSelf: \"stretch\",\n    minHeight: ICON_THUMB_1.height * 2.0,\n    maxHeight: ICON_THUMB_1.height * 2.0\n  },\n  playbackSlider: {\n    alignSelf: \"stretch\"\n  },\n  timestampRow: {\n    flex: 1,\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n    alignSelf: \"stretch\",\n    minHeight: FONT_SIZE\n  },\n  text: {\n    fontSize: FONT_SIZE,\n    minHeight: FONT_SIZE\n  },\n  buffering: {\n    textAlign: \"left\",\n    paddingLeft: 20\n  },\n  timestamp: {\n    textAlign: \"right\",\n    paddingRight: 20\n  },\n  button: {\n    backgroundColor: BACKGROUND_COLOR\n  },\n  buttonsContainerBase: {\n    flex: 1,\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\"\n  },\n  buttonsContainerTopRow: {\n    maxHeight: ICON_PLAY_BUTTON.height,\n    minWidth: DEVICE_WIDTH / 2.0,\n    maxWidth: DEVICE_WIDTH / 2.0\n  },\n  buttonsContainerMiddleRow: {\n    maxHeight: ICON_MUTED_BUTTON.height,\n    alignSelf: \"stretch\",\n    paddingRight: 20\n  },\n  volumeContainer: {\n    flex: 1,\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n    minWidth: DEVICE_WIDTH / 2.0,\n    maxWidth: DEVICE_WIDTH / 2.0\n  },\n  volumeSlider: {\n    width: DEVICE_WIDTH / 2.0 - ICON_MUTED_BUTTON.width\n  },\n  buttonsContainerBottomRow: {\n    maxHeight: 32,\n    alignSelf: \"stretch\",\n    paddingRight: 20,\n    paddingLeft: 20\n  },\n  rateSlider: {\n    width: DEVICE_WIDTH / 2.0\n  },\n  buttonsContainerTextRow: {\n    maxHeight: FONT_SIZE,\n    alignItems: \"center\",\n    paddingRight: 20,\n    paddingLeft: 20,\n    minWidth: DEVICE_WIDTH,\n    maxWidth: DEVICE_WIDTH\n  }\n});\n"
  },
  {
    "path": "README.md",
    "content": "# playlist example\n\nAn example app using the [Expo.Audio](https://docs.expo.io/versions/latest/sdk/audio/) & [Expo.Video](https://docs.expo.io/versions/latest/sdk/video/) API.\n\nSee [App.js](https://github.com/expo/playlist-example/blob/master/App.js) for the good stuff.\n\n---\n\n### Please report any issues at the [main Expo repository](https://github.com/expo/expo/issues)\n"
  },
  {
    "path": "app.json",
    "content": "{\n  \"expo\": {\n    \"name\": \"playlist-example\",\n    \"description\": \"a playlist of some songs that Greg likes to demo the Expo AV API.\",\n    \"scheme\": \"playlist-example\",\n    \"slug\": \"playlist\",\n    \"privacy\": \"public\",\n    \"version\": \"3.0.0\",\n    \"platforms\": [\"android\", \"ios\"],\n    \"githubUrl\": \"https://github.com/expo/playlist-example\",\n    \"orientation\": \"portrait\",\n    \"primaryColor\": \"#cccccc\",\n    \"icon\": \"./assets/images/icon.png\",\n    \"userInterfaceStyle\": \"light\",\n    \"splash\": {\n      \"backgroundColor\": \"#ffffff\",\n      \"image\": \"./assets/images/icon.png\",\n      \"resizeMode\": \"contain\"\n    },\n    \"packagerOpts\": {\n      \"assetExts\": [\"png\", \"ttf\"]\n    },\n    \"ios\": {\n      \"supportsTablet\": true\n    },\n    \"android\": {\n      \"adaptiveIcon\": {\n        \"foregroundImage\": \"./assets/images/icon.png\",\n        \"backgroundColor\": \"#ffffff\"\n      }\n    },\n    \"plugins\": [\n      \"expo-asset\",\n      \"expo-font\"\n    ]\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"playlist-example\",\n  \"version\": \"3.0.0\",\n  \"description\": \"a playlist of some songs that Greg likes to demo the Expo AV API.\",\n  \"author\": \"expo\",\n  \"keywords\": [\n    \"expo\",\n    \"react-native\",\n    \"audio\",\n    \"playlist\"\n  ],\n  \"private\": true,\n  \"main\": \"node_modules/expo/AppEntry.js\",\n  \"dependencies\": {\n    \"@react-native-community/slider\": \"4.5.2\",\n    \"expo\": \"~51.0.14\",\n    \"expo-asset\": \"~10.0.9\",\n    \"expo-av\": \"~14.0.5\",\n    \"expo-font\": \"~12.0.7\",\n    \"expo-updates\": \"~0.25.17\",\n    \"react\": \"18.2.0\",\n    \"react-native\": \"0.74.2\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.24.0\"\n  }\n}\n"
  }
]