Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { delay } from 'redux-saga'
- import { cancel, fork, select, call, put, takeEvery } from 'redux-saga/effects'
- import { Audio } from 'browser'
- import Channel from 'promise-channel'
- // List of all AudioElement node events we will be listening on and providing redux
- // dispatches for. All of these events will go out under the `AUDIO_SAGA:EVENT` type
- // but can be translated into individual events for better reducer support.
- const eventList = [
- 'ended',
- 'play',
- 'pause',
- 'playing',
- 'timeupdate',
- 'volumechange',
- 'waiting',
- 'seeking',
- 'seeked',
- 'stalled',
- 'suspend',
- 'canplay',
- 'loadedmetadata',
- 'change',
- 'progress',
- 'load',
- 'loadend',
- 'loadstart',
- 'loadeddata',
- 'durationchange'
- ]
- //
- // Action creators
- //
- // Action type constants
- const PLAY = 'AUDIO_SAGA:PLAY'
- const PAUSE = 'AUDIO_SAGA:PAUSE'
- const SEEK = 'AUDIO_SAGA:SEEK'
- const SOURCE = 'AUDIO_SAGA:SOURCE'
- const EVENT = 'AUDIO_SAGA:EVENT'
- // Origin values for `seek`
- export const SEEK_SET = 'SEEK_SET'
- export const SEEK_CUR = 'SEEK_CUR'
- export const actions = {
- play: () => {
- return {
- type: PLAY
- }
- },
- pause: () => {
- return {
- type: PAUSE
- }
- },
- seek: (offset, origin) => {
- return {
- type: SEEK,
- payload: { offset, origin }
- }
- },
- setSource: src => {
- return {
- type: SOURCE,
- payload: { src }
- }
- }
- }
- //
- // Saga methods that can be invoked via `call` effect
- //
- export function * play() {
- yield put(actions.play())
- }
- export function * pause() {
- yield put(actions.pause())
- }
- export function * seek(offset, origin = SEEK_CUR) {
- yield put(actions.seek(offset, origin))
- }
- export function * setSource(src) {
- yield put(actions.setSource(src))
- }
- // Converts generic AUDIO_SAGA:EVENT actions into seperate named actions.
- // This isn't really needed, and the user does not need to include this
- // middleware for AudioSaga to work. It's provided as a conviences.
- // It takes an option event array which can be used to only propagate a
- // selection of events
- export function createAudioSagaMiddleware(events = eventList) {
- return store => next => action => {
- if (action.type === EVENT && events.includes(action.payload.type)) {
- return store.dispatch({
- type: `AUDIO_SAGA_${action.payload.type.toUpperCase()}`,
- payload: {
- ...action.payload
- }
- })
- }
- return next(action)
- }
- }
- // Main saga that manages the audio node
- export default function * audio({ selectors }) {
- const audioChannel = new Channel()
- const audioNode = new Audio()
- audioNode.crossOrigin = 'anonymous'
- // Attach listners to all of the audio nodes events. The events are placed
- // into a promise channel which we will take from and dispatch to redux. This
- // is needed as we can't yield from the event listener itself, nor do we have
- // direct access (keeping to the API of saga) to the store dispatch.
- for (const eventName of eventList) {
- audioNode.addEventListener(eventName, event => {
- audioChannel.put(event)
- })
- }
- // Makes the audio node play from its current source
- yield takeEvery(PLAY, function * () {
- audioNode.play()
- })
- // Update the audio nodes source, continue playing if we are already playing
- yield takeEvery(SOURCE, function * ({ payload: { src } }) {
- const playing = yield select(selectors.playing)
- if (audioNode.src !== src) {
- audioNode.src = src
- audioNode.load()
- if (playing) {
- audioNode.play()
- }
- }
- })
- // Pause the audio node, and clear the sync timer to stop publishing time
- // events
- yield takeEvery(PAUSE, function * () {
- audioNode.pause()
- })
- // Binds a value between a min and a max
- function boundValue(min, max, value) {
- if (value < min) {
- return min
- } else if (value > max) {
- return max
- } else {
- return value
- }
- }
- // Seek the audio node to the desired position as a percentage of its duration
- yield takeEvery(SEEK, function * ({ payload: { offset, origin } }) {
- if (origin === SEEK_CUR) {
- audioNode.currentTime = boundValue(0, audio.duration, audioNode.currentTime + offset)
- } else if (origin === SEEK_SET) {
- audioNode.currentTime = boundValue(0, audio.duration, offset)
- }
- })
- // Main loop that converts the audio node events into redux events
- while (true) {
- const event = yield audioChannel.take()
- const { type, timeStamp, loaded, total } = event
- const { muted, paused, ended, duration, currentTime, currentSrc } = audioNode
- yield put({
- type: EVENT,
- payload: {
- type, timeStamp, muted, paused, ended, duration, currentTime, currentSrc, loaded, total
- }
- })
- }
- }
Add Comment
Please, Sign In to add comment