Expo Audio Error: The AVPlayerItem instance has failed with the error code -1102 and domain “NSURLErrorDomain”

Please provide the following:

  1. SDK Version: 36.0.0
  2. Platforms(Android/iOS/web/all): Android/IOs

I’m creating an audio player with Expo and pretty much everything works as expected. I can push goforward, goback, pause/play etc.

But if I listen to the whole audio file and I try to go to the next track at the end of that audio file, it fails with this error: “The AVPlayerItem instance has failed with the error code -1102 and domain “NSURLErrorDomain””

I’ve pretty much tried everything to fix this bug.

I’m loading the audio files from a remote uri.

Here is the code:

class PlaylistItem {
    constructor(identifier, name, uri, isVideo) {
        this.identifier = this.identifier;
        this.name = name;
        this.uri = uri;
        this.isVideo = isVideo;
    }
}

let SLIDER_ITEM_WIDTH = Math.round(Dimensions.get('window').width);

class PlayAudioScreen extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            showVideo: false,
            muted: false,
            playbackInstancePosition: null,
            playbackInstanceDuration: null,
            shouldPlay: false,
            isPlaying: false,
            isBuffering: false,
            isLoading: true,
            fontLoaded: false,
            shouldCorrectPitch: true,
            volume: 1.0,
            rate: 1.0,
            poster: false,
            useNativeControls: false,
            fullscreen: false,
            throughEarpiece: false,
            isReady: false,
            guid: null,
            startAt: 0,
            audioIsLoaded: false,
            showPresentation: false,
            finishedPresentationLoading: false,
        };
        this.playbackInstance = null;
        this.timeout = null;
        this.lastUpdate = new Date();
        this.lastUpdatedUsage = null;
        this.isChanging = false;
        this.index = 0;
        this.Playlist = props.navigation.state.params.items.map(item => {
            return new PlaylistItem(
                item.identifier,
                item.title,
                item.media,
                false
            );
        });
    }

    async componentDidMount() {
        const { items, start, id } = this.props.navigation.state.params;
        Audio.setAudioModeAsync({
            allowsRecordingIOS: false,
            interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
            playsInSilentModeIOS: true,
            shouldDuckAndroid: true,
            interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
            playThroughEarpieceAndroid: false,
        });
        this._loadNewPlaybackInstance(false);
    }

    async componentWillUnmount() {
        if (this.playbackInstance != null) {
            await this.playbackInstance.unloadAsync();
            // this.playbackInstance.setOnPlaybackStatusUpdate(null);
            this.playbackInstance = null;
        }
    }

    _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,
                audioIsLoaded: true,
            });
            if (status.didJustFinish && !status.isLooping) {
                this._advanceIndex(true);
                this._updatePlaybackInstanceForIndex(true);
            }
        } else {
            if (status.error) {
                console.log(`FATAL PLAYER ERROR: ${status.error}`);
            }
        }
    };

    async _updatePlaybackInstanceForIndex(playing) {
        this._updateScreenForLoading(true);
        this._loadNewPlaybackInstance(playing);
    }

    _updateScreenForLoading(isLoading) {
        if (isLoading) {
            this.setState({
                isPlaying: false,
                playbackInstanceDuration: 0,
                playbackInstancePosition: 0,
                audioIsLoaded: false,
            });
        } else {
            this.setState({
                audioIsLoaded: true,
            });
        }
    }

    _advanceIndex(forward) {
        this.index =
            (this.index + (forward ? 1 : this.Playlist.length - 1)) %
            this.Playlist.length;
    }

    async _loadNewPlaybackInstance(playing) {
        if (this.playbackInstance != null) {
            await this.playbackInstance.unloadAsync();
            // this.playbackInstance.setOnPlaybackStatusUpdate(null);
            this.playbackInstance = null;
        }

        const source = {
            uri: this.Playlist[this.index].uri,
        };

        const initialStatus = {
            shouldPlay: playing,
            rate: this.state.rate,
            shouldCorrectPitch: this.state.shouldCorrectPitch,
            volume: this.state.volume,
            isMuted: this.state.muted,
            startAt: this.state.startAt,
        };

        const GUID = uuid();

        const { sound, status } = await Audio.Sound.createAsync(
            source,
            initialStatus,
            this._onPlaybackStatusUpdate
        );
        this.playbackInstance = sound;
        this.setState({
            guid: GUID,
        });
    }

    _onPlayPausePressed = () => {
        if (this.playbackInstance != null) {
            if (this.state.isPlaying) {
                this.playbackInstance.pauseAsync();
            } else {
                this.playbackInstance.playAsync();
            }
        }
    };

    _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);
        }
    };

    _renderSliderItem = item => (
        <SliderItemWrapper key={item.identifier} width={SLIDER_ITEM_WIDTH}>
            <ShadowImage
                height={wp(88)}
                width={wp(88)}
                imageUrl={this.props.navigation.state.params.image}
                noToRadius
            />
        </SliderItemWrapper>
    );

    render() {
        const { isReady, audioIsLoaded } = this.state;
        const { title } = this.props.navigation.state.params;

        if (!isReady) {
            return (
                <>
                    <StatusBar
                        backgroundColor={colors.light}
                        barStyle="dark-content"
                    />
                    <View
                        style={{
                            flex: 1,
                            alignItems: 'center',
                            justifyContent: 'center',
                            backgroundColor: colors.light,
                        }}
                    ></View>
                </>
            );
        }

        return (
            <ScreenWrapper style={{ flex: 1, paddingBottom: 0 }}>
                <StatusBar
                    backgroundColor={colors.light}
                    barStyle="dark-content"
                />
                <MyHeader
                    showLeftContent
                    onLeftClick={() => this.props.navigation.goBack()}
                    backgroundColor={colors.light}
                />
                <TopSection>
                    <Container style={{ width: '100%', flex: 1 }}>
                        <ContentWrapper>
                            <TopSectionContainer>
                                <Slider
                                    renderItem={this._renderSliderItem}
                                    data={this.Playlist}
                                    totalLength={this.Playlist.length}
                                    initialScrollIndex={this.index}
                                    activeIndex={this.index}
                                    keyExtractor={(item, index) =>
                                        item.identifier
                                    }
                                    itemWidth={SLIDER_ITEM_WIDTH}
                                />
                            </TopSectionContainer>
                            <BottomSection disabled={!audioIsLoaded}>
                                <PlayActiveHeadingContainer>
                                    <Heading1>{`${
                                        this.Playlist[this.index].name
                                    }`}</Heading1>
                                    <Paragraph>{`${this.index + 1}/${
                                        this.Playlist.length
                                    } ${title}`}</Paragraph>
                                    <PlayerTrack
                                        playedMillis={
                                            this.state.playbackInstancePosition
                                        }
                                        durationMillis={
                                            this.state.playbackInstanceDuration
                                        }
                                        disabled={!audioIsLoaded}
                                    />
                                </PlayActiveHeadingContainer>
                                <PlayerControls
                                    onBackFunc={this._onBackPressed}
                                    onForwardFunc={this._onForwardPressed}
                                    isPlaying={this.state.isPlaying}
                                    onPress={this._onPlayPausePressed}
                                    leftDisabled={this.index === 0}
                                    rightDisabled={
                                        this.index === this.Playlist.length - 1
                                    }
                                    disabled={!audioIsLoaded}
                                />
                            </BottomSection>
                        </ContentWrapper>
                    </Container>
                </TopSection>
            </ScreenWrapper>
        );
    }
}
2 Likes

My best guess is this is because the media files are loaded from a remote url. Has anyone had problems with this?

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.