Skip to content

Commit

Permalink
Creating functionality to download and play podcasts from filesystem
Browse files Browse the repository at this point in the history
  • Loading branch information
steniowagner committed Feb 14, 2019
1 parent 2a3727f commit 232cab2
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 37 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.json
Expand Up @@ -12,7 +12,9 @@
"no-underscore-dangle": 0,
"import/no-cycle": 0,
"no-alert": 0,
"react-native/split-platform-components": 0,
"import/no-extraneous-dependencies": 0,
"import/no-dynamic-require": 0,
"react/jsx-max-props-per-line": [1, { "maximum": 1, "when": "always" }],
"react/jsx-first-prop-new-line": [1, "always"],
"react-native/no-inline-styles": 0,
Expand Down
2 changes: 2 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Expand Up @@ -3,6 +3,8 @@

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<application
android:name=".MainApplication"
Expand Down
19 changes: 12 additions & 7 deletions src/components/common/SoundComponent.js
Expand Up @@ -8,6 +8,7 @@ import { connect } from 'react-redux';
import { Creators as PlayerCreators } from '~/store/ducks/player';

type PlayerProps = {
currentPodcastURI: Object,
paused: boolean,
};

Expand All @@ -22,22 +23,26 @@ const SoundComponent = ({
nextPodcast,
player,
}: Props): Object => {
const { paused } = player;

return (
const { currentPodcastURI, paused } = player;
console.tron.log(currentPodcastURI);
return currentPodcastURI ? (
<Sound
source={require('./teste.mp3')}
source={{
uri: currentPodcastURI,
}}
onError={() => console.tron.log('errr')}
onBuffer={() => console.tron.log('onBuffer')}
playInBackground
paused={paused}
paused={false}
repeat={false}
audioOnly
rate={1.0}
ignoreSilentSwitch="ignore"
onLoad={() => console.tron.log('loaded')}
onLoad={() => console.tron.log('onLoad')}
onProgress={({ currentTime }) => setCurrentTime(currentTime)}
onEnd={() => nextPodcast()}
/>
);
) : null;
};

const mapDispatchToProps = dispatch => bindActionCreators(PlayerCreators, dispatch);
Expand Down
Binary file added src/components/common/asd.mp3
Binary file not shown.
14 changes: 12 additions & 2 deletions src/components/screens/player/index.js
Expand Up @@ -3,11 +3,16 @@
import React, { Component } from 'react';
import { View } from 'react-native';

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Creators as PlayerCreators } from '~/store/ducks/player';

import PlayerControls from './components/PlayerControls';

class Player extends Component<{}, {}> {
componentDidMount() {
console.tron.log('');
const { setPodcast } = this.props;
setPodcast();
}

render() {
Expand All @@ -26,4 +31,9 @@ class Player extends Component<{}, {}> {
}
}

export default Player;
const mapDispatchToProps = dispatch => bindActionCreators(PlayerCreators, dispatch);

export default connect(
null,
mapDispatchToProps,
)(Player);
49 changes: 42 additions & 7 deletions src/store/ducks/player.js
Expand Up @@ -5,20 +5,39 @@ export const Types = {
NEXT_PODCAST: 'player/NEXT_PODCAST',
PREVIOUS_PODCAST: 'player/PREVIOUS_PODCAST',
SET_PODCAST: 'player/SET_PODCAST',
SET_PODCAST_SUCCESS: 'player/SET_PODCAST_SUCCESS',
};

const podcasts = [
{
title: 'OH NANANA',
id: 1,
url: 'https://s3-sa-east-1.amazonaws.com/gonative/1.mp3',
},
{
title: 'eu vou cair',
id: 2,
path: './cair.mp3',
},
];

const INITIAL_STATE = {
currentPodcast: null,
podcastInfo: null,
currentPodcastURI: null,
currentTime: '00:00',
playlist: podcasts,
playlistIndex: 0,
paused: true,
error: false,
playlist: [],
};

export const Creators = {
setPodcast: () => ({
type: Types.PLAY_PODCAST,
type: Types.SET_PODCAST,
}),

setPodcastSuccess: currentPodcastURI => ({
type: Types.SET_PODCAST_SUCCESS,
payload: { currentPodcastURI },
}),

setCurrentTime: currentTime => ({
Expand Down Expand Up @@ -46,18 +65,18 @@ export const Creators = {
const parseCurrentPodcastTime = (rawTime) => {
const currentTime = Math.ceil(rawTime);

const currentTimeInMinutes = currentTime / 60;
const currentTimeInMinutes = Math.floor(currentTime / 60);
const currentTimeInSeconds = currentTime % 60;

let minutes = '00';
let seconds = '00';

if (currentTimeInMinutes > 9) {
minutes = Math.floor(currentTimeInMinutes);
minutes = currentTimeInMinutes;
}

if (currentTimeInMinutes >= 1 && currentTimeInMinutes <= 9) {
minutes = `0${Math.floor(currentTimeInMinutes)}`;
minutes = `0${currentTimeInMinutes}`;
}

if (currentTimeInSeconds > 9 && currentTimeInSeconds <= 59) {
Expand All @@ -77,6 +96,17 @@ const parseCurrentPodcastTime = (rawTime) => {

const player = (state = INITIAL_STATE, { type, payload }) => {
switch (type) {
case Types.SET_PODCAST:
return {
...state,
};

case Types.SET_PODCAST_SUCCESS:
return {
...state,
currentPodcastURI: payload.currentPodcastURI,
};

case Types.PLAY_PODCAST:
return {
...state,
Expand All @@ -95,6 +125,11 @@ const player = (state = INITIAL_STATE, { type, payload }) => {
currentTime: parseCurrentPodcastTime(payload.currentTime),
};

case Types.PREVIOUS_PODCAST:
return {
...state,
};

default:
return state;
}
Expand Down
85 changes: 64 additions & 21 deletions src/store/sagas/player.js
@@ -1,29 +1,72 @@
import { call, put } from 'redux-saga/effects';
import { call, select, put } from 'redux-saga/effects';
import { Platform } from 'react-native';
import RNFS from 'react-native-fs';

import {
persistItemInStorage,
getItemFromStorage,
KEYS,
} from '~/utils/AsyncStorageManager';
import {
requestPermission,
PERMISSIONS_TYPES,
} from '~/utils/AndroidPermissionsManager';

import { Creators as PlayerCreators } from '../ducks/player';

const FILE_PREFIX = Platform.OS === 'android' ? 'file://' : '';

const _isPodcastAlreadyCached = async (currentPodcast) => {
const rawPodcastsSaved = await getItemFromStorage(KEYS.PODCAST, []);

const podcastsSaved = typeof rawPodcastsSaved === 'string'
? JSON.parse(rawPodcastsSaved)
: rawPodcastsSaved;

const podcastCached = podcastsSaved.filter(
podcast => podcast.id === currentPodcast.id,
)[0];

return podcastCached;
};

export function* setPodcast() {
try {
} catch (err) {}
}
const { playlistIndex, playlist } = yield select(state => state.player);
const currentPodcast = playlist[playlistIndex];

export function* playPrevious() {
/* const { podcast, playlist } = yield select(state => state.player);
const previousPodcastIndex = playlist.findIndex(podcastFromPlaylist => podcastFromPlaylist._id === podcast._id) - 1;
yield requestPermission(PERMISSIONS_TYPES.READ_EXTERNAL_STORAGE);

try {
yield cps(RNSound.getCurrentTime, PODCAST_ID);
} catch (currentTimeInSeconds) {
const shouldRepeatSamePodcast = currentTimeInSeconds > 3;
if (shouldRepeatSamePodcast) {
yield put(ActionCreators.playerSetPodcastRequest(podcast, playlist));
return;
}
if (previousPodcastIndex === -1) {
yield put(ActionCreators.playerSetPodcastRequest(playlist[0], playlist));
} else {
yield put(ActionCreators.playerSetPodcastRequest(playlist[previousPodcastIndex], playlist));
}
} */
/* yield persistItemInStorage(
KEYS.PODCAST,
JSON.stringify([
{
...currentPodcast,
path: `${FILE_PREFIX}${RNFS.ExternalDirectoryPath}/${
currentPodcast.id
}.mp3`,
},
]),
); */

/* yield call(RNFS.downloadFile, {
fromUrl: 'https://s3-sa-east-1.amazonaws.com/gonative/1.mp3',
toFile: `${FILE_PREFIX}${RNFS.ExternalDirectoryPath}/${
currentPodcast.id
}.mp3`,
discretionary: true,
}); */

const podcastCached = yield _isPodcastAlreadyCached(currentPodcast);

const isPodcastAlreadyCached = !!podcastCached
&& !!podcastCached.path
&& typeof podcastCached.path === 'string';

const podcastURI = isPodcastAlreadyCached
? podcastCached.path
: currentPodcast.url;

yield put(PlayerCreators.setPodcastSuccess(podcastURI));
} catch (err) {}
}
48 changes: 48 additions & 0 deletions src/utils/AndroidPermissionsManager.js
@@ -0,0 +1,48 @@
import { PermissionsAndroid } from 'react-native';

export const PERMISSIONS_TYPES = {
WRITE_EXTERNAL_STORAGE: 'WRITE_EXTERNAL_STORAGE',
READ_EXTERNAL_STORAGE: 'READ_EXTERNAL_STORAGE',
};

const PERMISSIONS = {
[PERMISSIONS_TYPES.WRITE_EXTERNAL_STORAGE]: {
type: 'WRITE_EXTERNAL_STORAGE',
config: {
title: 'MindCast App Create File Permission',
message:
'MindCast needs access to your storage '
+ 'so you can save your podcasts locally.',
},
},

[PERMISSIONS_TYPES.READ_EXTERNAL_STORAGE]: {
type: 'READ_EXTERNAL_STORAGE',
config: {
title: 'MindCast App Read File Permission',
message:
'MindCast needs access to your storage '
+ 'so you can listen your podcasts locally and offline.',
},
},
};

export const requestPermission = async (type) => {
try {
const permissionConfig = PERMISSIONS[type];

const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS[type],
{
...permissionConfig.config,
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);

return granted === PermissionsAndroid.RESULTS.GRANTED;
} catch (err) {
console.tron.log(err);
}
};
43 changes: 43 additions & 0 deletions src/utils/AsyncStorageManager.js
@@ -0,0 +1,43 @@
// @flow

import { AsyncStorage } from 'react-native';

const APP_STORAGE_KEY = '@MIND_CAST';

export const KEYS = {
PODCAST: 'PODCAST',
};

export const getItemFromStorage = async (
key: string,
defaultValue: any,
): any => {
try {
const value = (await AsyncStorage.getItem(`${APP_STORAGE_KEY}:${key}`)) || defaultValue;

return value;
} catch (error) {
console.tron.log(error);
}

return defaultValue;
};

export const persistItemInStorage = async (
key: string,
value: any,
): Promise<void> => {
try {
await AsyncStorage.setItem(`${APP_STORAGE_KEY}:${key}`, value.toString());
} catch (err) {
console.tron.log(err);
}
};

export const removeItemFromStorage = async (key: string) => {
try {
await AsyncStorage.removeItem(`${APP_STORAGE_KEY}:${key}`);
} catch (err) {
console.tron.log(err);
}
};

0 comments on commit 232cab2

Please sign in to comment.