import { call , put , select , takeLatest } from 'redux-saga/effects' ; import { RECORDING_FORMAT_VERSION } from '../version' ; import { asyncGetJson , asyncRequestJson } from '../utils/api' ; import { getBlob , uploadBlob } from '../utils/blobs' ; import { ActionTypes } from "./actionTypes" ; import { ActionTypes as RecorderActionTypes } from "../recorder/actionTypes" ; import { ActionTypes as CommonActionTypes } from "../common/actionTypes" ; import { ActionTypes as AppActionTypes } from "../actionTypes" ; import { getRecorderState } from "./selectors" ; import { AppStore , CodecastOptions } from "../store" ; import { Bundle } from "../linker" ; import { App } from "../index" ; import { Screen } from "../common/screens" ; export type CodecastRecord = { version : string ; events : any [ ] ; options : CodecastOptions ; subtitles : any [ ] ; } ; export enum SaveStep { EncodingPending = 'encoding pending' , EncodingDone = 'encoding done' , UploadPreparing = 'upload preparing' , UploadEventsPending = 'upload events pending' , UploadEventsDone = 'upload events done' , UploadAudioPending = 'upload audio pending' , UploadAudioDone = 'upload audio done' , Done = 'done' , Error = 'error' } export const initialStateSave = { step : null as SaveStep , progress : 0 , audioUrl : '' , wavAudioUrl : '' , eventsUrl : '' , playerUrl : '' , editorUrl : '' , error : '' as any , } ; export default function ( bundle ) { bundle . addReducer ( AppActionTypes . AppInit , ( state ) => { state . save = { ... initialStateSave } ; } ) ; bundle . defineAction ( ActionTypes . SaveScreenEncodingStart ) ; bundle . addReducer ( ActionTypes . SaveScreenEncodingStart , ( state ) => { state . save . step = SaveStep . EncodingPending ; state . save . progress = 0 ; state . save . playerUrl = '' ; state . save . editorUrl = '' ; } ) ; bundle . defineAction ( ActionTypes . SaveScreenEncodingProgress ) ; bundle . addReducer ( ActionTypes . SaveScreenEncodingProgress , ( state , { payload : progress } ) => { state . save . progress = progress ; } ) ; bundle . defineAction ( ActionTypes . SaveScreenEncodingDone ) ; bundle . addReducer ( ActionTypes . SaveScreenEncodingDone , ( state , { payload : { audioUrl , wavAudioUrl , eventsUrl } } ) => { state . save . step = SaveStep . EncodingDone ; state . save . audioUrl = audioUrl ; state . save . wavAudioUrl = wavAudioUrl ; state . save . eventsUrl = eventsUrl ; state . save . progress = 0 ; } ) ; bundle . defineAction ( ActionTypes . SaveScreenUpload ) ; bundle . defineAction ( ActionTypes . SaveScreenPreparing ) ; bundle . addReducer ( ActionTypes . SaveScreenPreparing , ( state ) => { state . save . step = SaveStep . UploadPreparing ; } ) ; bundle . defineAction ( ActionTypes . SaveScreenEventsUploading ) ; bundle . addReducer ( ActionTypes . SaveScreenEventsUploading , ( state ) => { state . save . step = SaveStep . UploadEventsPending ; } ) ; bundle . defineAction ( ActionTypes . SaveScreenEventsUploaded ) ; bundle . addReducer ( ActionTypes . SaveScreenEventsUploaded , ( state , { payload : { url } } ) => { state . save . step = SaveStep . UploadEventsDone ; state . save . eventsUrl = url ; } ) ; bundle . defineAction ( ActionTypes . SaveScreenAudioUploading ) ; bundle . addReducer ( ActionTypes . SaveScreenAudioUploading , ( state ) => { state . save . step = SaveStep . UploadAudioPending ; } ) ; bundle . defineAction ( ActionTypes . SaveScreenAudioUploaded ) ; bundle . addReducer ( ActionTypes . SaveScreenAudioUploaded , ( state , { payload : { url } } ) => { state . save . step = SaveStep . UploadAudioDone ; state . save . audioUrl = url ; } ) ; bundle . defineAction ( ActionTypes . SaveScreenUploadSucceeded ) ; bundle . addReducer ( ActionTypes . SaveScreenUploadSucceeded , ( state , { payload : { playerUrl , editorUrl } } ) => { state . save . step = SaveStep . Done ; state . save . playerUrl = playerUrl ; state . save . editorUrl = editorUrl ; } ) ; bundle . defineAction ( ActionTypes . SaveScreenUploadFailed ) ; bundle . addReducer ( ActionTypes . SaveScreenUploadFailed , ( state , { payload : { error } } ) => { state . save . step = SaveStep . Error ; state . save . error = error ; } ) ; bundle . addSaga ( function * saveSaga ( arg ) { yield takeLatest ( RecorderActionTypes . RecorderStopped , encodingSaga ) ; yield takeLatest ( RecorderActionTypes . SaveScreenEncodingDone , ensureLoggedSaga ) ; yield takeLatest ( ActionTypes . SaveScreenUpload , uploadSaga , arg ) ; } ) ; } export function * ensureLoggedSaga ( ) { const state = yield select ( ) ; const { baseUrl } = state . options ; const response = yield call ( asyncGetJson , baseUrl + '/me' , [ ] ) ; if ( response . user ) { yield put ( { type : CommonActionTypes . LoginFeedback , payload : { user : response . user } } ) ; } else { yield put ( { type : CommonActionTypes . LoginFeedback , payload : { user : false } } ) ; } } function * encodingSaga ( ) { yield put ( { type : ActionTypes . SaveScreenEncodingStart , payload : { } } ) ; yield put ( { type : CommonActionTypes . AppSwitchToScreen , payload : { screen : Screen . Save } } ) ; const state = yield select ( ) ; const recorder = getRecorderState ( state ) ; const { worker } = recorder . context ; const { mp3 , wav , duration } = yield call ( worker . call , 'export' , { mp3 : true , wav : true } , encodingProgressSaga ) ; const mp3Url = URL . createObjectURL ( mp3 ) ; const wavUrl = URL . createObjectURL ( wav ) ; const version = RECORDING_FORMAT_VERSION ; const endTime = Math . floor ( duration * 1000 ) ; let events = [ ... recorder . events ] ; events . push ( [ endTime , 'end' ] ) ; const subtitles = [ ] ; const options = state . options ; const data = { version , options , events , subtitles } as CodecastRecord ; const eventsBlob = new Blob ( [ JSON . stringify ( data ) ] , { type : "application/json;charset=UTF-8" } ) ; const eventsUrl = URL . createObjectURL ( eventsBlob ) ; yield put ( { type : ActionTypes . SaveScreenEncodingDone , payload : { audioUrl : mp3Url , wavAudioUrl : wavUrl , eventsUrl : eventsUrl , } } ) ; function * encodingProgressSaga ( { step , progress } ) { yield put ( { type : ActionTypes . SaveScreenEncodingProgress , payload : { step , progress } } ) ; } } function * uploadSaga ( app , action ) { try { yield put ( { type : ActionTypes . SaveScreenPreparing } ) ; const state = yield select ( ) ; const save = state . save ; const response = yield call ( asyncRequestJson , 'upload' , action . payload . target ) ; if ( response . error ) { throw new Error ( `cannot upload: ${ response . error }` ) ; } yield put ( { type : ActionTypes . SaveScreenEventsUploading } ) ; const eventsBlob = yield call ( getBlob , save . eventsUrl ) ; yield call ( uploadBlob , response . events , eventsBlob ) ; yield put ( { type : ActionTypes . SaveScreenEventsUploaded , payload : { url : response . events . public_url } } ) ; yield put ( { type : ActionTypes . SaveScreenAudioUploading } ) ; const audioBlob = yield call ( getBlob , save . audioUrl ) ; yield call ( uploadBlob , response . audio , audioBlob ) ; yield put ( { type : ActionTypes . SaveScreenAudioUploaded , payload : { url : response . audio . public_url } } ) ; yield put ( { type : ActionTypes . SaveScreenUploadSucceeded , payload : { playerUrl : response . player_url , editorUrl : response . editor_url } } ) ; } catch ( error ) { yield put ( { type : ActionTypes . SaveScreenUploadFailed , payload : { error } } ) ; } }