Repository: expo/playlist-example
Branch: master
Commit: 8b91bbf0ea13
Files: 6
Total size: 25.9 KB
Directory structure:
gitextract_b6274l6n/
├── .babelrc
├── .gitignore
├── App.js
├── README.md
├── app.json
└── package.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": ["babel-preset-expo"]
}
================================================
FILE: .gitignore
================================================
node_modules/**/*
.expo/*
npm-debug.*
================================================
FILE: App.js
================================================
/**
* @flow
*/
import React from "react";
import {
Dimensions,
Image,
StyleSheet,
Text,
TouchableHighlight,
View
} from "react-native";
import { Asset } from "expo-asset";
import {
Audio,
InterruptionModeAndroid,
InterruptionModeIOS,
ResizeMode,
Video
} from "expo-av";
import * as Font from "expo-font";
import Slider from "@react-native-community/slider";
import { MaterialIcons } from "@expo/vector-icons";
class Icon {
constructor(module, width, height) {
this.module = module;
this.width = width;
this.height = height;
Asset.fromModule(this.module).downloadAsync();
}
}
class PlaylistItem {
constructor(name, uri, isVideo) {
this.name = name;
this.uri = uri;
this.isVideo = isVideo;
}
}
const PLAYLIST = [
new PlaylistItem(
"Comfort Fit - “Sorry”",
"https://s3.amazonaws.com/exp-us-standard/audio/playlist-example/Comfort_Fit_-_03_-_Sorry.mp3",
false
),
new PlaylistItem(
"Big Buck Bunny",
"http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4",
true
),
new PlaylistItem(
"Mildred Bailey – “All Of Me”",
"https://ia800304.us.archive.org/34/items/PaulWhitemanwithMildredBailey/PaulWhitemanwithMildredBailey-AllofMe.mp3",
false
),
new PlaylistItem(
"Popeye - I don't scare",
"https://ia800501.us.archive.org/11/items/popeye_i_dont_scare/popeye_i_dont_scare_512kb.mp4",
true
),
new PlaylistItem(
"Podington Bear - “Rubber Robot”",
"https://s3.amazonaws.com/exp-us-standard/audio/playlist-example/Podington_Bear_-_Rubber_Robot.mp3",
false
)
];
const ICON_THROUGH_EARPIECE = "speaker-phone";
const ICON_THROUGH_SPEAKER = "speaker";
const ICON_PLAY_BUTTON = new Icon(
require("./assets/images/play_button.png"),
34,
51
);
const ICON_PAUSE_BUTTON = new Icon(
require("./assets/images/pause_button.png"),
34,
51
);
const ICON_STOP_BUTTON = new Icon(
require("./assets/images/stop_button.png"),
22,
22
);
const ICON_FORWARD_BUTTON = new Icon(
require("./assets/images/forward_button.png"),
33,
25
);
const ICON_BACK_BUTTON = new Icon(
require("./assets/images/back_button.png"),
33,
25
);
const ICON_LOOP_ALL_BUTTON = new Icon(
require("./assets/images/loop_all_button.png"),
77,
35
);
const ICON_LOOP_ONE_BUTTON = new Icon(
require("./assets/images/loop_one_button.png"),
77,
35
);
const ICON_MUTED_BUTTON = new Icon(
require("./assets/images/muted_button.png"),
67,
58
);
const ICON_UNMUTED_BUTTON = new Icon(
require("./assets/images/unmuted_button.png"),
67,
58
);
const ICON_TRACK_1 = new Icon(require("./assets/images/track_1.png"), 166, 5);
const ICON_THUMB_1 = new Icon(require("./assets/images/thumb_1.png"), 18, 19);
const ICON_THUMB_2 = new Icon(require("./assets/images/thumb_2.png"), 15, 19);
const LOOPING_TYPE_ALL = 0;
const LOOPING_TYPE_ONE = 1;
const LOOPING_TYPE_ICONS = { 0: ICON_LOOP_ALL_BUTTON, 1: ICON_LOOP_ONE_BUTTON };
const { width: DEVICE_WIDTH, height: DEVICE_HEIGHT } = Dimensions.get("window");
const BACKGROUND_COLOR = "#FFF8ED";
const DISABLED_OPACITY = 0.5;
const FONT_SIZE = 14;
const LOADING_STRING = "... loading ...";
const BUFFERING_STRING = "...buffering...";
const RATE_SCALE = 3.0;
const VIDEO_CONTAINER_HEIGHT = (DEVICE_HEIGHT * 2.0) / 5.0 - FONT_SIZE * 2;
export default class App extends React.Component {
constructor(props) {
super(props);
this.index = 0;
this.isSeeking = false;
this.shouldPlayAtEndOfSeek = false;
this.playbackInstance = null;
this.state = {
showVideo: false,
playbackInstanceName: LOADING_STRING,
loopingType: LOOPING_TYPE_ALL,
muted: false,
playbackInstancePosition: null,
playbackInstanceDuration: null,
shouldPlay: false,
isPlaying: false,
isBuffering: false,
isLoading: true,
fontLoaded: false,
shouldCorrectPitch: true,
volume: 1.0,
rate: 1.0,
videoWidth: DEVICE_WIDTH,
videoHeight: VIDEO_CONTAINER_HEIGHT,
poster: false,
useNativeControls: false,
fullscreen: false,
throughEarpiece: false
};
}
componentDidMount() {
Audio.setAudioModeAsync({
allowsRecordingIOS: false,
staysActiveInBackground: false,
interruptionModeIOS: InterruptionModeIOS.DoNotMix,
playsInSilentModeIOS: true,
shouldDuckAndroid: true,
interruptionModeAndroid: InterruptionModeAndroid.DoNotMix,
playThroughEarpieceAndroid: false
});
(async () => {
await Font.loadAsync({
...MaterialIcons.font,
"cutive-mono-regular": require("./assets/fonts/CutiveMono-Regular.ttf")
});
this.setState({ fontLoaded: true });
})();
}
async _loadNewPlaybackInstance(playing) {
if (this.playbackInstance != null) {
await this.playbackInstance.unloadAsync();
// this.playbackInstance.setOnPlaybackStatusUpdate(null);
this.playbackInstance = null;
}
const source = { uri: PLAYLIST[this.index].uri };
const initialStatus = {
shouldPlay: playing,
rate: this.state.rate,
shouldCorrectPitch: this.state.shouldCorrectPitch,
volume: this.state.volume,
isMuted: this.state.muted,
isLooping: this.state.loopingType === LOOPING_TYPE_ONE
// // UNCOMMENT THIS TO TEST THE OLD androidImplementation:
// androidImplementation: 'MediaPlayer',
};
if (PLAYLIST[this.index].isVideo) {
await this._video.loadAsync(source, initialStatus);
// this._video.onPlaybackStatusUpdate(this._onPlaybackStatusUpdate);
this.playbackInstance = this._video;
const status = await this._video.getStatusAsync();
} else {
const { sound, status } = await Audio.Sound.createAsync(
source,
initialStatus,
this._onPlaybackStatusUpdate
);
this.playbackInstance = sound;
}
this._updateScreenForLoading(false);
}
_mountVideo = component => {
this._video = component;
this._loadNewPlaybackInstance(false);
};
_updateScreenForLoading(isLoading) {
if (isLoading) {
this.setState({
showVideo: false,
isPlaying: false,
playbackInstanceName: LOADING_STRING,
playbackInstanceDuration: null,
playbackInstancePosition: null,
isLoading: true
});
} else {
this.setState({
playbackInstanceName: PLAYLIST[this.index].name,
showVideo: PLAYLIST[this.index].isVideo,
isLoading: false
});
}
}
_onPlaybackStatusUpdate = status => {
if (status.isLoaded) {
this.setState({
playbackInstancePosition: status.positionMillis,
playbackInstanceDuration: status.durationMillis,
shouldPlay: status.shouldPlay,
isPlaying: status.isPlaying,
isBuffering: status.isBuffering,
rate: status.rate,
muted: status.isMuted,
volume: status.volume,
loopingType: status.isLooping ? LOOPING_TYPE_ONE : LOOPING_TYPE_ALL,
shouldCorrectPitch: status.shouldCorrectPitch
});
if (status.didJustFinish && !status.isLooping) {
this._advanceIndex(true);
this._updatePlaybackInstanceForIndex(true);
}
} else {
if (status.error) {
console.log(`FATAL PLAYER ERROR: ${status.error}`);
}
}
};
_onLoadStart = () => {
console.log(`ON LOAD START`);
};
_onLoad = status => {
console.log(`ON LOAD : ${JSON.stringify(status)}`);
};
_onError = error => {
console.log(`ON ERROR : ${error}`);
};
_onReadyForDisplay = event => {
const widestHeight =
(DEVICE_WIDTH * event.naturalSize.height) / event.naturalSize.width;
if (widestHeight > VIDEO_CONTAINER_HEIGHT) {
this.setState({
videoWidth:
(VIDEO_CONTAINER_HEIGHT * event.naturalSize.width) /
event.naturalSize.height,
videoHeight: VIDEO_CONTAINER_HEIGHT
});
} else {
this.setState({
videoWidth: DEVICE_WIDTH,
videoHeight:
(DEVICE_WIDTH * event.naturalSize.height) / event.naturalSize.width
});
}
};
_onFullscreenUpdate = event => {
console.log(
`FULLSCREEN UPDATE : ${JSON.stringify(event.fullscreenUpdate)}`
);
};
_advanceIndex(forward) {
this.index =
(this.index + (forward ? 1 : PLAYLIST.length - 1)) % PLAYLIST.length;
}
async _updatePlaybackInstanceForIndex(playing) {
this._updateScreenForLoading(true);
this.setState({
videoWidth: DEVICE_WIDTH,
videoHeight: VIDEO_CONTAINER_HEIGHT
});
this._loadNewPlaybackInstance(playing);
}
_onPlayPausePressed = () => {
if (this.playbackInstance != null) {
if (this.state.isPlaying) {
this.playbackInstance.pauseAsync();
} else {
this.playbackInstance.playAsync();
}
}
};
_onStopPressed = () => {
if (this.playbackInstance != null) {
this.playbackInstance.stopAsync();
}
};
_onForwardPressed = () => {
if (this.playbackInstance != null) {
this._advanceIndex(true);
this._updatePlaybackInstanceForIndex(this.state.shouldPlay);
}
};
_onBackPressed = () => {
if (this.playbackInstance != null) {
this._advanceIndex(false);
this._updatePlaybackInstanceForIndex(this.state.shouldPlay);
}
};
_onMutePressed = () => {
if (this.playbackInstance != null) {
this.playbackInstance.setIsMutedAsync(!this.state.muted);
}
};
_onLoopPressed = () => {
if (this.playbackInstance != null) {
this.playbackInstance.setIsLoopingAsync(
this.state.loopingType !== LOOPING_TYPE_ONE
);
}
};
_onVolumeSliderValueChange = value => {
if (this.playbackInstance != null) {
this.playbackInstance.setVolumeAsync(value);
}
};
_trySetRate = async (rate, shouldCorrectPitch) => {
if (this.playbackInstance != null) {
try {
await this.playbackInstance.setRateAsync(rate, shouldCorrectPitch);
} catch (error) {
// Rate changing could not be performed, possibly because the client's Android API is too old.
}
}
};
_onRateSliderSlidingComplete = async value => {
this._trySetRate(value * RATE_SCALE, this.state.shouldCorrectPitch);
};
_onPitchCorrectionPressed = async value => {
this._trySetRate(this.state.rate, !this.state.shouldCorrectPitch);
};
_onSeekSliderValueChange = value => {
if (this.playbackInstance != null && !this.isSeeking) {
this.isSeeking = true;
this.shouldPlayAtEndOfSeek = this.state.shouldPlay;
this.playbackInstance.pauseAsync();
}
};
_onSeekSliderSlidingComplete = async value => {
if (this.playbackInstance != null) {
this.isSeeking = false;
const seekPosition = value * this.state.playbackInstanceDuration;
if (this.shouldPlayAtEndOfSeek) {
this.playbackInstance.playFromPositionAsync(seekPosition);
} else {
this.playbackInstance.setPositionAsync(seekPosition);
}
}
};
_getSeekSliderPosition() {
if (
this.playbackInstance != null &&
this.state.playbackInstancePosition != null &&
this.state.playbackInstanceDuration != null
) {
return (
this.state.playbackInstancePosition /
this.state.playbackInstanceDuration
);
}
return 0;
}
_getMMSSFromMillis(millis) {
const totalSeconds = millis / 1000;
const seconds = Math.floor(totalSeconds % 60);
const minutes = Math.floor(totalSeconds / 60);
const padWithZero = number => {
const string = number.toString();
if (number < 10) {
return "0" + string;
}
return string;
};
return padWithZero(minutes) + ":" + padWithZero(seconds);
}
_getTimestamp() {
if (
this.playbackInstance != null &&
this.state.playbackInstancePosition != null &&
this.state.playbackInstanceDuration != null
) {
return `${this._getMMSSFromMillis(
this.state.playbackInstancePosition
)} / ${this._getMMSSFromMillis(this.state.playbackInstanceDuration)}`;
}
return "";
}
_onPosterPressed = () => {
this.setState({ poster: !this.state.poster });
};
_onUseNativeControlsPressed = () => {
this.setState({ useNativeControls: !this.state.useNativeControls });
};
_onFullscreenPressed = () => {
try {
this._video.presentFullscreenPlayer();
} catch (error) {
console.log(error.toString());
}
};
_onSpeakerPressed = () => {
this.setState(
state => {
return { throughEarpiece: !state.throughEarpiece };
},
() =>
Audio.setAudioModeAsync({
allowsRecordingIOS: false,
interruptionModeIOS: InterruptionModeIOS.DoNotMix,
playsInSilentModeIOS: true,
shouldDuckAndroid: true,
interruptionModeAndroid: InterruptionModeAndroid.DoNotMix,
playThroughEarpieceAndroid: this.state.throughEarpiece
})
);
};
render() {
return !this.state.fontLoaded ? (
<View style={styles.emptyContainer} />
) : (
<View style={styles.container}>
<View />
<View style={styles.nameContainer}>
<Text style={[styles.text, { fontFamily: "cutive-mono-regular" }]}>
{this.state.playbackInstanceName}
</Text>
</View>
<View style={styles.space} />
<View style={styles.videoContainer}>
<Video
ref={this._mountVideo}
style={[
styles.video,
{
opacity: this.state.showVideo ? 1.0 : 0.0,
width: this.state.videoWidth,
height: this.state.videoHeight
}
]}
resizeMode={ResizeMode.CONTAIN}
onPlaybackStatusUpdate={this._onPlaybackStatusUpdate}
onLoadStart={this._onLoadStart}
onLoad={this._onLoad}
onError={this._onError}
onFullscreenUpdate={this._onFullscreenUpdate}
onReadyForDisplay={this._onReadyForDisplay}
useNativeControls={this.state.useNativeControls}
/>
</View>
<View
style={[
styles.playbackContainer,
{
opacity: this.state.isLoading ? DISABLED_OPACITY : 1.0
}
]}
>
<Slider
style={styles.playbackSlider}
trackImage={ICON_TRACK_1.module}
thumbImage={ICON_THUMB_1.module}
value={this._getSeekSliderPosition()}
onValueChange={this._onSeekSliderValueChange}
onSlidingComplete={this._onSeekSliderSlidingComplete}
disabled={this.state.isLoading}
/>
<View style={styles.timestampRow}>
<Text
style={[
styles.text,
styles.buffering,
{ fontFamily: "cutive-mono-regular" }
]}
>
{this.state.isBuffering ? BUFFERING_STRING : ""}
</Text>
<Text
style={[
styles.text,
styles.timestamp,
{ fontFamily: "cutive-mono-regular" }
]}
>
{this._getTimestamp()}
</Text>
</View>
</View>
<View
style={[
styles.buttonsContainerBase,
styles.buttonsContainerTopRow,
{
opacity: this.state.isLoading ? DISABLED_OPACITY : 1.0
}
]}
>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={this._onBackPressed}
disabled={this.state.isLoading}
>
<Image style={styles.button} source={ICON_BACK_BUTTON.module} />
</TouchableHighlight>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={this._onPlayPausePressed}
disabled={this.state.isLoading}
>
<Image
style={styles.button}
source={
this.state.isPlaying
? ICON_PAUSE_BUTTON.module
: ICON_PLAY_BUTTON.module
}
/>
</TouchableHighlight>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={this._onStopPressed}
disabled={this.state.isLoading}
>
<Image style={styles.button} source={ICON_STOP_BUTTON.module} />
</TouchableHighlight>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={this._onForwardPressed}
disabled={this.state.isLoading}
>
<Image style={styles.button} source={ICON_FORWARD_BUTTON.module} />
</TouchableHighlight>
</View>
<View
style={[
styles.buttonsContainerBase,
styles.buttonsContainerMiddleRow
]}
>
<View style={styles.volumeContainer}>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={this._onMutePressed}
>
<Image
style={styles.button}
source={
this.state.muted
? ICON_MUTED_BUTTON.module
: ICON_UNMUTED_BUTTON.module
}
/>
</TouchableHighlight>
<Slider
style={styles.volumeSlider}
trackImage={ICON_TRACK_1.module}
thumbImage={ICON_THUMB_2.module}
value={1}
onValueChange={this._onVolumeSliderValueChange}
/>
</View>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={this._onLoopPressed}
>
<Image
style={styles.button}
source={LOOPING_TYPE_ICONS[this.state.loopingType].module}
/>
</TouchableHighlight>
</View>
<View
style={[
styles.buttonsContainerBase,
styles.buttonsContainerBottomRow
]}
>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={() => this._trySetRate(1.0, this.state.shouldCorrectPitch)}
>
<View style={styles.button}>
<Text
style={[styles.text, { fontFamily: "cutive-mono-regular" }]}
>
Rate:
</Text>
</View>
</TouchableHighlight>
<Slider
style={styles.rateSlider}
trackImage={ICON_TRACK_1.module}
thumbImage={ICON_THUMB_1.module}
value={this.state.rate / RATE_SCALE}
onSlidingComplete={this._onRateSliderSlidingComplete}
/>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={this._onPitchCorrectionPressed}
>
<View style={styles.button}>
<Text
style={[styles.text, { fontFamily: "cutive-mono-regular" }]}
>
PC: {this.state.shouldCorrectPitch ? "yes" : "no"}
</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={this._onSpeakerPressed}
underlayColor={BACKGROUND_COLOR}
>
<MaterialIcons
name={
this.state.throughEarpiece
? ICON_THROUGH_EARPIECE
: ICON_THROUGH_SPEAKER
}
size={32}
color="black"
/>
</TouchableHighlight>
</View>
<View />
{this.state.showVideo ? (
<View>
<View
style={[
styles.buttonsContainerBase,
styles.buttonsContainerTextRow
]}
>
<View />
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={this._onPosterPressed}
>
<View style={styles.button}>
<Text
style={[styles.text, { fontFamily: "cutive-mono-regular" }]}
>
Poster: {this.state.poster ? "yes" : "no"}
</Text>
</View>
</TouchableHighlight>
<View />
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={this._onFullscreenPressed}
>
<View style={styles.button}>
<Text
style={[styles.text, { fontFamily: "cutive-mono-regular" }]}
>
Fullscreen
</Text>
</View>
</TouchableHighlight>
<View />
</View>
<View style={styles.space} />
<View
style={[
styles.buttonsContainerBase,
styles.buttonsContainerTextRow
]}
>
<View />
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={this._onUseNativeControlsPressed}
>
<View style={styles.button}>
<Text
style={[styles.text, { fontFamily: "cutive-mono-regular" }]}
>
Native Controls:{" "}
{this.state.useNativeControls ? "yes" : "no"}
</Text>
</View>
</TouchableHighlight>
<View />
</View>
</View>
) : null}
</View>
);
}
}
const styles = StyleSheet.create({
emptyContainer: {
alignSelf: "stretch",
backgroundColor: BACKGROUND_COLOR
},
container: {
flex: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "center",
alignSelf: "stretch",
backgroundColor: BACKGROUND_COLOR
},
wrapper: {},
nameContainer: {
height: FONT_SIZE
},
space: {
height: FONT_SIZE
},
videoContainer: {
height: VIDEO_CONTAINER_HEIGHT
},
video: {
maxWidth: DEVICE_WIDTH
},
playbackContainer: {
flex: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "center",
alignSelf: "stretch",
minHeight: ICON_THUMB_1.height * 2.0,
maxHeight: ICON_THUMB_1.height * 2.0
},
playbackSlider: {
alignSelf: "stretch"
},
timestampRow: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
alignSelf: "stretch",
minHeight: FONT_SIZE
},
text: {
fontSize: FONT_SIZE,
minHeight: FONT_SIZE
},
buffering: {
textAlign: "left",
paddingLeft: 20
},
timestamp: {
textAlign: "right",
paddingRight: 20
},
button: {
backgroundColor: BACKGROUND_COLOR
},
buttonsContainerBase: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between"
},
buttonsContainerTopRow: {
maxHeight: ICON_PLAY_BUTTON.height,
minWidth: DEVICE_WIDTH / 2.0,
maxWidth: DEVICE_WIDTH / 2.0
},
buttonsContainerMiddleRow: {
maxHeight: ICON_MUTED_BUTTON.height,
alignSelf: "stretch",
paddingRight: 20
},
volumeContainer: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
minWidth: DEVICE_WIDTH / 2.0,
maxWidth: DEVICE_WIDTH / 2.0
},
volumeSlider: {
width: DEVICE_WIDTH / 2.0 - ICON_MUTED_BUTTON.width
},
buttonsContainerBottomRow: {
maxHeight: 32,
alignSelf: "stretch",
paddingRight: 20,
paddingLeft: 20
},
rateSlider: {
width: DEVICE_WIDTH / 2.0
},
buttonsContainerTextRow: {
maxHeight: FONT_SIZE,
alignItems: "center",
paddingRight: 20,
paddingLeft: 20,
minWidth: DEVICE_WIDTH,
maxWidth: DEVICE_WIDTH
}
});
================================================
FILE: README.md
================================================
# playlist example
An 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.
See [App.js](https://github.com/expo/playlist-example/blob/master/App.js) for the good stuff.
---
### Please report any issues at the [main Expo repository](https://github.com/expo/expo/issues)
================================================
FILE: app.json
================================================
{
"expo": {
"name": "playlist-example",
"description": "a playlist of some songs that Greg likes to demo the Expo AV API.",
"scheme": "playlist-example",
"slug": "playlist",
"privacy": "public",
"version": "3.0.0",
"platforms": ["android", "ios"],
"githubUrl": "https://github.com/expo/playlist-example",
"orientation": "portrait",
"primaryColor": "#cccccc",
"icon": "./assets/images/icon.png",
"userInterfaceStyle": "light",
"splash": {
"backgroundColor": "#ffffff",
"image": "./assets/images/icon.png",
"resizeMode": "contain"
},
"packagerOpts": {
"assetExts": ["png", "ttf"]
},
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/icon.png",
"backgroundColor": "#ffffff"
}
},
"plugins": [
"expo-asset",
"expo-font"
]
}
}
================================================
FILE: package.json
================================================
{
"name": "playlist-example",
"version": "3.0.0",
"description": "a playlist of some songs that Greg likes to demo the Expo AV API.",
"author": "expo",
"keywords": [
"expo",
"react-native",
"audio",
"playlist"
],
"private": true,
"main": "node_modules/expo/AppEntry.js",
"dependencies": {
"@react-native-community/slider": "4.5.2",
"expo": "~51.0.14",
"expo-asset": "~10.0.9",
"expo-av": "~14.0.5",
"expo-font": "~12.0.7",
"expo-updates": "~0.25.17",
"react": "18.2.0",
"react-native": "0.74.2"
},
"devDependencies": {
"@babel/core": "^7.24.0"
}
}
gitextract_b6274l6n/ ├── .babelrc ├── .gitignore ├── App.js ├── README.md ├── app.json └── package.json
SYMBOL INDEX (40 symbols across 1 files)
FILE: App.js
class Icon (line 27) | class Icon {
method constructor (line 28) | constructor(module, width, height) {
class PlaylistItem (line 36) | class PlaylistItem {
method constructor (line 37) | constructor(name, uri, isVideo) {
constant PLAYLIST (line 44) | const PLAYLIST = [
constant ICON_THROUGH_EARPIECE (line 72) | const ICON_THROUGH_EARPIECE = "speaker-phone";
constant ICON_THROUGH_SPEAKER (line 73) | const ICON_THROUGH_SPEAKER = "speaker";
constant ICON_PLAY_BUTTON (line 75) | const ICON_PLAY_BUTTON = new Icon(
constant ICON_PAUSE_BUTTON (line 80) | const ICON_PAUSE_BUTTON = new Icon(
constant ICON_STOP_BUTTON (line 85) | const ICON_STOP_BUTTON = new Icon(
constant ICON_FORWARD_BUTTON (line 90) | const ICON_FORWARD_BUTTON = new Icon(
constant ICON_BACK_BUTTON (line 95) | const ICON_BACK_BUTTON = new Icon(
constant ICON_LOOP_ALL_BUTTON (line 101) | const ICON_LOOP_ALL_BUTTON = new Icon(
constant ICON_LOOP_ONE_BUTTON (line 106) | const ICON_LOOP_ONE_BUTTON = new Icon(
constant ICON_MUTED_BUTTON (line 112) | const ICON_MUTED_BUTTON = new Icon(
constant ICON_UNMUTED_BUTTON (line 117) | const ICON_UNMUTED_BUTTON = new Icon(
constant ICON_TRACK_1 (line 123) | const ICON_TRACK_1 = new Icon(require("./assets/images/track_1.png"), 16...
constant ICON_THUMB_1 (line 124) | const ICON_THUMB_1 = new Icon(require("./assets/images/thumb_1.png"), 18...
constant ICON_THUMB_2 (line 125) | const ICON_THUMB_2 = new Icon(require("./assets/images/thumb_2.png"), 15...
constant LOOPING_TYPE_ALL (line 127) | const LOOPING_TYPE_ALL = 0;
constant LOOPING_TYPE_ONE (line 128) | const LOOPING_TYPE_ONE = 1;
constant LOOPING_TYPE_ICONS (line 129) | const LOOPING_TYPE_ICONS = { 0: ICON_LOOP_ALL_BUTTON, 1: ICON_LOOP_ONE_B...
constant BACKGROUND_COLOR (line 132) | const BACKGROUND_COLOR = "#FFF8ED";
constant DISABLED_OPACITY (line 133) | const DISABLED_OPACITY = 0.5;
constant FONT_SIZE (line 134) | const FONT_SIZE = 14;
constant LOADING_STRING (line 135) | const LOADING_STRING = "... loading ...";
constant BUFFERING_STRING (line 136) | const BUFFERING_STRING = "...buffering...";
constant RATE_SCALE (line 137) | const RATE_SCALE = 3.0;
constant VIDEO_CONTAINER_HEIGHT (line 138) | const VIDEO_CONTAINER_HEIGHT = (DEVICE_HEIGHT * 2.0) / 5.0 - FONT_SIZE * 2;
class App (line 140) | class App extends React.Component {
method constructor (line 141) | constructor(props) {
method componentDidMount (line 171) | componentDidMount() {
method _loadNewPlaybackInstance (line 190) | async _loadNewPlaybackInstance(playing) {
method _updateScreenForLoading (line 231) | _updateScreenForLoading(isLoading) {
method _advanceIndex (line 312) | _advanceIndex(forward) {
method _updatePlaybackInstanceForIndex (line 317) | async _updatePlaybackInstanceForIndex(playing) {
method _getSeekSliderPosition (line 416) | _getSeekSliderPosition() {
method _getMMSSFromMillis (line 430) | _getMMSSFromMillis(millis) {
method _getTimestamp (line 445) | _getTimestamp() {
method render (line 491) | render() {
Condensed preview — 6 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (28K chars).
[
{
"path": ".babelrc",
"chars": 39,
"preview": "{\n \"presets\": [\"babel-preset-expo\"]\n}\n"
},
{
"path": ".gitignore",
"chars": 38,
"preview": "node_modules/**/*\n.expo/*\nnpm-debug.*\n"
},
{
"path": "App.js",
"chars": 24553,
"preview": "/**\n * @flow\n */\n\nimport React from \"react\";\nimport {\n Dimensions,\n Image,\n StyleSheet,\n Text,\n TouchableHighlight,"
},
{
"path": "README.md",
"chars": 373,
"preview": "# playlist example\n\nAn example app using the [Expo.Audio](https://docs.expo.io/versions/latest/sdk/audio/) & [Expo.Video"
},
{
"path": "app.json",
"chars": 935,
"preview": "{\n \"expo\": {\n \"name\": \"playlist-example\",\n \"description\": \"a playlist of some songs that Greg likes to demo the E"
},
{
"path": "package.json",
"chars": 624,
"preview": "{\n \"name\": \"playlist-example\",\n \"version\": \"3.0.0\",\n \"description\": \"a playlist of some songs that Greg likes to demo"
}
]
About this extraction
This page contains the full source code of the expo/playlist-example GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 6 files (25.9 KB), approximately 6.4k tokens, and a symbol index with 40 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.