Guest User

Untitled

a guest
Dec 14th, 2017
138
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 4.61 KB | None | 0 0
  1. import { delay } from 'redux-saga'
  2. import { cancel, fork, select, call, put, takeEvery } from 'redux-saga/effects'
  3.  
  4. import { Audio } from 'browser'
  5. import Channel from 'promise-channel'
  6.  
  7. // List of all AudioElement node events we will be listening on and providing redux
  8. // dispatches for. All of these events will go out under the `AUDIO_SAGA:EVENT` type
  9. // but can be translated into individual events for better reducer support.
  10. const eventList = [
  11. 'ended',
  12. 'play',
  13. 'pause',
  14. 'playing',
  15. 'timeupdate',
  16. 'volumechange',
  17. 'waiting',
  18. 'seeking',
  19. 'seeked',
  20. 'stalled',
  21. 'suspend',
  22. 'canplay',
  23. 'loadedmetadata',
  24. 'change',
  25. 'progress',
  26. 'load',
  27. 'loadend',
  28. 'loadstart',
  29. 'loadeddata',
  30. 'durationchange'
  31. ]
  32.  
  33. //
  34. // Action creators
  35. //
  36.  
  37. // Action type constants
  38. const PLAY = 'AUDIO_SAGA:PLAY'
  39. const PAUSE = 'AUDIO_SAGA:PAUSE'
  40. const SEEK = 'AUDIO_SAGA:SEEK'
  41. const SOURCE = 'AUDIO_SAGA:SOURCE'
  42. const EVENT = 'AUDIO_SAGA:EVENT'
  43.  
  44. // Origin values for `seek`
  45. export const SEEK_SET = 'SEEK_SET'
  46. export const SEEK_CUR = 'SEEK_CUR'
  47.  
  48. export const actions = {
  49. play: () => {
  50. return {
  51. type: PLAY
  52. }
  53. },
  54. pause: () => {
  55. return {
  56. type: PAUSE
  57. }
  58. },
  59. seek: (offset, origin) => {
  60. return {
  61. type: SEEK,
  62. payload: { offset, origin }
  63. }
  64. },
  65. setSource: src => {
  66. return {
  67. type: SOURCE,
  68. payload: { src }
  69. }
  70. }
  71. }
  72.  
  73. //
  74. // Saga methods that can be invoked via `call` effect
  75. //
  76.  
  77. export function * play() {
  78. yield put(actions.play())
  79. }
  80.  
  81. export function * pause() {
  82. yield put(actions.pause())
  83. }
  84.  
  85. export function * seek(offset, origin = SEEK_CUR) {
  86. yield put(actions.seek(offset, origin))
  87. }
  88.  
  89. export function * setSource(src) {
  90. yield put(actions.setSource(src))
  91. }
  92.  
  93. // Converts generic AUDIO_SAGA:EVENT actions into seperate named actions.
  94. // This isn't really needed, and the user does not need to include this
  95. // middleware for AudioSaga to work. It's provided as a conviences.
  96. // It takes an option event array which can be used to only propagate a
  97. // selection of events
  98. export function createAudioSagaMiddleware(events = eventList) {
  99. return store => next => action => {
  100. if (action.type === EVENT && events.includes(action.payload.type)) {
  101. return store.dispatch({
  102. type: `AUDIO_SAGA_${action.payload.type.toUpperCase()}`,
  103. payload: {
  104. ...action.payload
  105. }
  106. })
  107. }
  108.  
  109. return next(action)
  110. }
  111. }
  112.  
  113. // Main saga that manages the audio node
  114. export default function * audio({ selectors }) {
  115. const audioChannel = new Channel()
  116. const audioNode = new Audio()
  117.  
  118. audioNode.crossOrigin = 'anonymous'
  119.  
  120. // Attach listners to all of the audio nodes events. The events are placed
  121. // into a promise channel which we will take from and dispatch to redux. This
  122. // is needed as we can't yield from the event listener itself, nor do we have
  123. // direct access (keeping to the API of saga) to the store dispatch.
  124. for (const eventName of eventList) {
  125. audioNode.addEventListener(eventName, event => {
  126. audioChannel.put(event)
  127. })
  128. }
  129.  
  130. // Makes the audio node play from its current source
  131. yield takeEvery(PLAY, function * () {
  132. audioNode.play()
  133. })
  134.  
  135. // Update the audio nodes source, continue playing if we are already playing
  136. yield takeEvery(SOURCE, function * ({ payload: { src } }) {
  137. const playing = yield select(selectors.playing)
  138.  
  139. if (audioNode.src !== src) {
  140. audioNode.src = src
  141. audioNode.load()
  142.  
  143. if (playing) {
  144. audioNode.play()
  145. }
  146. }
  147. })
  148.  
  149. // Pause the audio node, and clear the sync timer to stop publishing time
  150. // events
  151. yield takeEvery(PAUSE, function * () {
  152. audioNode.pause()
  153. })
  154.  
  155. // Binds a value between a min and a max
  156. function boundValue(min, max, value) {
  157. if (value < min) {
  158. return min
  159. } else if (value > max) {
  160. return max
  161. } else {
  162. return value
  163. }
  164. }
  165.  
  166. // Seek the audio node to the desired position as a percentage of its duration
  167. yield takeEvery(SEEK, function * ({ payload: { offset, origin } }) {
  168. if (origin === SEEK_CUR) {
  169. audioNode.currentTime = boundValue(0, audio.duration, audioNode.currentTime + offset)
  170. } else if (origin === SEEK_SET) {
  171. audioNode.currentTime = boundValue(0, audio.duration, offset)
  172. }
  173. })
  174.  
  175. // Main loop that converts the audio node events into redux events
  176. while (true) {
  177. const event = yield audioChannel.take()
  178.  
  179. const { type, timeStamp, loaded, total } = event
  180. const { muted, paused, ended, duration, currentTime, currentSrc } = audioNode
  181.  
  182. yield put({
  183. type: EVENT,
  184. payload: {
  185. type, timeStamp, muted, paused, ended, duration, currentTime, currentSrc, loaded, total
  186. }
  187. })
  188. }
  189. }
Add Comment
Please, Sign In to add comment