tlenclos / react-native-audio-streaming

iOS & Android react native module to play an audio stream, with background support and media controls
MIT License
784 stars 254 forks source link

Art Work picture #34

Open openGeeksLab opened 8 years ago

openGeeksLab commented 8 years ago

Could you add artwork of artist? Thank you.

JoJordens commented 7 years ago

Does anybody have this working?

tlenclos commented 7 years ago

@JoJordens this is not implemented and I have no time at the moment to work on this module, but that should not be hard

For iOS https://github.com/tlenclos/react-native-audio-streaming/blob/master/ios/ReactNativeAudioStreaming.m#L438 For Android (add an image view and setters to update it) https://github.com/tlenclos/react-native-audio-streaming/blob/master/android/src/main/res/layout/streaming_notification_player.xml#L7

gwitteveen commented 7 years ago

This is possible by using the iTunes API and a little modification of the Player Component in the provided example.

This basically ads a getArtwork function which fetches the artwork from iTunes. The song state is a bit modified and now contains 'artist, title & artwork'

Artwork images are usually available at the following sizes:

In the now added Image append the URL with the size and .jpg like:

${artwork}80x80.jpg

import React, { Component } from 'react';
import {
    NativeModules,
    StyleSheet,
    Text,
    View,
    Image,
    TouchableOpacity,
    DeviceEventEmitter,
    ActivityIndicator,
    Platform
} from 'react-native';

const { ReactNativeAudioStreaming } = NativeModules;

// Possibles states
const PLAYING = 'PLAYING';
const STREAMING = 'STREAMING';
const PAUSED = 'PAUSED';
const STOPPED = 'STOPPED';
const ERROR = 'ERROR';
const METADATA_UPDATED = 'METADATA_UPDATED';
const BUFFERING = 'BUFFERING';
const START_PREPARING = 'START_PREPARING'; // Android only
const BUFFERING_START = 'BUFFERING_START'; // Android only

// UI
const iconSize = 60;

class Player extends Component {
    constructor(props) {
        super(props);
        this._onPress = this._onPress.bind(this);
        this._getArtwork = this._getArtwork.bind(this);

        this.state = {
            status: STOPPED,
            song: ''
        };
    }

    componentDidMount() {
        this.subscription = DeviceEventEmitter.addListener(
            'AudioBridgeEvent', (evt) => {
                // We just want meta update for song name
                if (evt.status === METADATA_UPDATED && evt.key === 'StreamTitle') {
                    this._getArtwork(evt.value)
                    .then(song => this.setState({song: song}))
                } else if (evt.status != METADATA_UPDATED) {
                    this.setState(evt);
                }
            }
        );

        ReactNativeAudioStreaming.getStatus((error, status) => {
            (error) ? console.log(error) : this.setState(status)
        });
    }

    _getArtwork(metaData){
        // This asumes the metadata is passed to function as 'artist - title'
        // Modify if needed
        const [ artist, title ] = metaData.split(' - ');
        let song = { artist: artist, title: title, artwork:''}
        return fetch(`https://itunes.apple.com/search?term=${escape(artist)} ${escape(title)}&limit=1&entity=song`)
        .then(blob => blob.json())
        .then(response => {
                // Some streams start with a jingle,
                // at that moment there is no song info,
                // but itunes returns a result when the api is called with an empty sting
                if(artist === ''){
                    song['artwork'] = undefined
                    return song;
                } else if(!response.results[0] || !response.results[0].artworkUrl100) {
                    song['artwork'] = undefined
                    return song;
                } else {
                    song['artwork'] = response.results[0].artworkUrl100.slice(0, -13)
                    return song
                }
            })
        .catch(error => {
            song['artwork'] = undefined
            return song;
        });
    }

    _onPress() {
        switch (this.state.status) {
            case PLAYING:
            case STREAMING:
                ReactNativeAudioStreaming.pause();
                break;
            case PAUSED:
                ReactNativeAudioStreaming.resume();
                break;
            case STOPPED:
            case ERROR:
                ReactNativeAudioStreaming.play(this.props.url, {showIniOSMediaCenter: true, showInAndroidNotifications: true});
                break;
            case BUFFERING:
                ReactNativeAudioStreaming.stop();
                break;
        }
    }

    render() {
        let icon = null;
        switch (this.state.status) {
            case PLAYING:
            case STREAMING:
                icon = <Text style={styles.icon}>॥</Text>;
                break;
            case PAUSED:
            case STOPPED:
            case ERROR:
                icon = <Text style={styles.icon}>▸</Text>;
                break;
            case BUFFERING:
            case BUFFERING_START:
            case START_PREPARING:
                icon = <ActivityIndicator
                    animating={true}
                    style={{height: 80}}
                    size="large"
                />;
                break;
        }

        const { title, artist, artwork } = this.state.song

        return (
                <TouchableOpacity style={styles.container} onPress={this._onPress}>
                    {icon}
                    <View style={styles.textContainer}>
                        <Text style={styles.songName}>{title}</Text>
                        <Text style={styles.songName}>{artist}</Text>
                    </View>
                    { artwork !== undefined && <Image resizeMode='cover' style={styles.artwork} source={{uri:`${artwork}80x80.jpg`}} />}
                </TouchableOpacity>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        position: 'absolute',
        bottom: 0,
        left: 0,
        right: 0,
        alignItems: 'center',
        flexDirection: 'row',
        height: 80,
        paddingLeft: 10,
        paddingRight: 10,
        borderColor: '#000033',
        borderTopWidth: 1,
    },
    icon: {
        color: '#000',
        fontSize: 26,
        borderColor: '#000033',
        borderWidth: 1,
        borderRadius: iconSize / 2,
        width: iconSize,
        height: Platform.OS == 'ios' ? iconSize : 40,
        justifyContent: 'center',
        alignItems: 'center',
        textAlign: 'center',
        paddingTop: Platform.OS == 'ios' ? 10 : 0
    },
    textContainer: {
        flexDirection: 'column',
        margin: 10,
        flex:1,
    },
    textLive: {
        color: '#000',
        marginBottom: 5
    },
    songName: {
        fontSize: 20,
        textAlign: 'center',
        color: '#000'
    },
    artwork: {
        height:80,
        width:80,
    }
});

Player.propTypes = {
    url: React.PropTypes.string.isRequired
};

export { Player, ReactNativeAudioStreaming }
miclaus commented 7 years ago

+1

in contrast to @gwitteveen's code, I didn't use .slice(0, -13), but hackily replaced parts of the URL to receive the image over HTTPS, since it's required by iOS's "App Transport Security Settings".

const artworkUrl = response.results[0].artworkUrl600
.replace(/http:\/\//, 'https://')
.replace(/\.mzstatic/, '-ssl.mzstatic')
<Image source={{ uri: artworkUrl }} />

i will pursue a different solution in the near future though, as this is not maintainable.