Created
September 5, 2018 20:41
-
-
Save pedropapa/dd05f3de8270ec1adb15183b06c3c749 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { DirectoryEntry, File, FileEntry } from '@ionic-native/file'; | |
import * as async from 'async'; | |
import { BehaviorSubject } from 'rxjs/BehaviorSubject'; | |
import { Observable } from 'rxjs/Observable'; | |
import { ReplaySubject } from 'rxjs/ReplaySubject'; | |
import 'rxjs/add/observable/throw'; | |
import { from } from 'rxjs/observable/from'; | |
const VIDEO_INFO_STORAGE_KEY = 'JBRJ-VIDEO-INFO-'; | |
const FILE_CHUNK_SIZE = 50000000; // 50mb | |
function getFileNameFromUrl(url: string) { | |
let fileName: string = url.match(/\/([\w,\-]+\.\w+)$/)[1]; | |
if (!fileName) { | |
return url; | |
} | |
return fileName.toLowerCase(); | |
} | |
export function abort(ish: BehaviorSubject<FileDownload>): Observable<FileDownload> { | |
ish.value._request.abort(); | |
// abortando download dos chunks do arquivo | |
if (ish.value.chunk$ instanceof ReplaySubject) { | |
ish.value.chunk$ | |
.subscribe((requests: XMLHttpRequest[]) => { | |
for (let ind in requests) { | |
if (requests[ind] instanceof XMLHttpRequest) { | |
requests[ind].abort(); | |
} | |
} | |
}); | |
} | |
return from(ish); | |
} | |
export function remove(ish: BehaviorSubject<FileDownload>): Observable<FileDownload> { | |
let file = ish.value.fileProvider; | |
file.removeFile(file.dataDirectory, getFileNameFromUrl(ish.value.url)) | |
.then(() => localStorage.removeItem(VIDEO_INFO_STORAGE_KEY + getFileNameFromUrl(ish.value.url))) | |
.catch((err) => ish.error(err)); | |
return from(ish); | |
} | |
export function fileSize(ish: BehaviorSubject<FileDownload>): Observable<FileDownload> { | |
let request: XMLHttpRequest = ish.value._request; | |
request.open('HEAD', ish.value.url, false); | |
request.onreadystatechange = function () { | |
if (this.readyState === this.DONE) { | |
ish.next(Object.assign(ish.value, { | |
fileSize: request.getResponseHeader('Content-Length'), | |
})); | |
} | |
}; | |
request.send(); | |
return from(ish); | |
} | |
export function useCache(ish: BehaviorSubject<FileDownload>): Observable<FileDownload> { | |
console.log('>>>>> useCache ', ish.value); | |
console.log('>>>>> useCache fileSize ', ish.value.fileSize); | |
let file = ish.value.fileProvider; | |
let existingInfoStored = localStorage.getItem(VIDEO_INFO_STORAGE_KEY + getFileNameFromUrl(ish.value.url)); | |
if (existingInfoStored) { | |
let fileInfo = JSON.parse(existingInfoStored); | |
file.resolveDirectoryUrl(file.dataDirectory) | |
.then((directoryEntry: DirectoryEntry) => { | |
file.getFile(directoryEntry, getFileNameFromUrl(ish.value.url), {}) | |
.then((localFile: FileEntry) => { | |
ish.next(Object.assign(ish.value, { | |
existingInfoStored: fileInfo, | |
status: FileDownloadStatus.ENDED, | |
fileSize: fileInfo.fileSize, | |
localFileUri: localFile.nativeURL, | |
})); | |
ish.complete(); | |
}, (err) => {}); | |
}, (err) => ish.error(err)); | |
} | |
return from(ish); | |
} | |
export function download(ish: BehaviorSubject<FileDownload>): Observable<FileDownload> { | |
console.log('>>>>> download ', ish.value); | |
let fileSize: number = parseInt(ish.value.fileSize + '', 10); | |
let onprogress$ = new BehaviorSubject<number>(0); | |
let bytesDownloaded = 0; | |
let chunkBytesDownloaded = 0; | |
function downloadChunk(range: string) { | |
let request: XMLHttpRequest = new XMLHttpRequest(); | |
request.open('GET', ish.value.url, true); | |
request.responseType = 'blob'; | |
request.setRequestHeader('Range', 'bytes=' + range); | |
request.addEventListener('load', () => { | |
console.log('terminou!!'); | |
bytesDownloaded += chunkBytesDownloaded; | |
}); | |
request.onprogress = (event: ProgressEvent) => { | |
if (ish.value.localFileUri) { | |
ish.value._request.abort(); | |
} | |
chunkBytesDownloaded = event.loaded; | |
onprogress$.next(bytesDownloaded + event.loaded); | |
}; | |
request.onerror = (event: Event) => { | |
ish.error(event); | |
}; | |
return request; | |
} | |
let bytesToDownload = 0; | |
let observables: XMLHttpRequest[] = []; | |
console.log('>>>> bytesToDownload ', bytesToDownload, fileSize, ish.value.fileSize); | |
while (bytesToDownload < fileSize) { | |
console.log('>>>>> download while ', bytesToDownload, fileSize); | |
let rangeBytes = bytesToDownload + FILE_CHUNK_SIZE; | |
if (rangeBytes >= fileSize) { | |
rangeBytes = fileSize; | |
} | |
observables.push( | |
downloadChunk(bytesToDownload + '-' + rangeBytes), | |
); | |
bytesToDownload = rangeBytes + 1; // .. | |
} | |
console.log('Observables ', observables); | |
let chunk$ = new ReplaySubject<XMLHttpRequest[]>(); | |
chunk$.next(observables); | |
ish.next(Object.assign(ish.value, { | |
status: FileDownloadStatus.STARTED, | |
onprogress$: onprogress$, | |
chunk$: chunk$, | |
})); | |
return from(ish); | |
} | |
export function saveLocal(ish: BehaviorSubject<FileDownload>): Observable<FileDownload> { | |
let file = ish.value.fileProvider; | |
let chunk$ = ish.value.chunk$; | |
let onsave$ = new BehaviorSubject<FileDownload>(ish.value); | |
async.waterfall([ | |
(cb: Function) => { | |
file.removeFile(file.dataDirectory, getFileNameFromUrl(ish.value.url)) | |
.then(() => cb(null), | |
() => cb(null)); | |
}, | |
(cb: Function) => { | |
file.createFile(file.dataDirectory, getFileNameFromUrl(ish.value.url), true) | |
.then((fileEntry: FileEntry) => cb(null, fileEntry), | |
(err) => cb(err)); | |
}, | |
], (err, fileEntry: FileEntry) => { | |
if (err) { | |
return ish.error(err); | |
} | |
return saveChunks(fileEntry); | |
}); | |
let saveChunks = (fileEntry: FileEntry) => chunk$.subscribe((requests: XMLHttpRequest[]) => { | |
console.log('>>>> chunks ', requests); | |
if (!requests.length) { | |
return false; | |
} | |
let promises = []; | |
let addPromise = (request: XMLHttpRequest) => { | |
promises.push((cb: Function) => { | |
request.onload = (() => { | |
console.log('>>>> chunk downloaded '); | |
let responseData = request.response; | |
if (responseData) { | |
const blob: Blob = new Blob([responseData], {type: 'video/mp4'}); | |
console.log('Começando a escrever'); | |
fileEntry.createWriter((fileWriter) => { | |
fileWriter.seek(fileWriter.length); | |
console.log('debug 1'); | |
fileWriter.onerror = (e) => { | |
console.log('Failed file write: ' + e.toString()); | |
cb(e); | |
}; | |
function writeFinish() { | |
console.log('terminando de escrever'); | |
function success() { | |
console.log('>>>> chunk written file'); | |
cb(null); | |
} | |
function fail(error) { | |
cb('Unable to retrieve file properties: ' + error.code); | |
} | |
fileEntry.file(success, fail); | |
} | |
let BLOCK_SIZE = 1024 * 1024; // write 1MB every time of write | |
let written = 0; | |
function writeNext(cbFinish) { | |
fileWriter.onwrite = function (evt) { | |
if (written < blob.size) { | |
writeNext(cbFinish); | |
} else { | |
cbFinish(); | |
} | |
}; | |
if (written) { | |
fileWriter.seek(fileWriter.length); | |
} | |
fileWriter.write(blob.slice(written, written + Math.min(BLOCK_SIZE, blob.size - written))); | |
written += Math.min(BLOCK_SIZE, blob.size - written); | |
} | |
writeNext(writeFinish); | |
}); | |
} else { | |
cb('NO DATA RECEIVED'); | |
} | |
}); | |
request.send(0); | |
}); | |
}; | |
for (let ind in requests) { | |
if (requests[ind]) { | |
let request: XMLHttpRequest = requests[ind]; | |
addPromise(request); | |
} | |
} | |
async.series(promises, () => { | |
console.log('Finalizando!'); | |
localStorage.setItem(VIDEO_INFO_STORAGE_KEY + getFileNameFromUrl(ish.value.url), JSON.stringify({ | |
fileSize: ish.value.fileSize, | |
})); | |
onsave$.next(Object.assign(ish.value, { | |
status: FileDownloadStatus.SAVED, | |
localFileUri: fileEntry.nativeURL, | |
})); | |
}); | |
}); | |
ish.next(Object.assign(ish.value, { | |
onsave$: onsave$, | |
})); | |
return from(ish); | |
} | |
export interface FileDownload { | |
_request: XMLHttpRequest; | |
url: string; | |
fileProvider: File; | |
fileSize?: number; | |
onprogress$?: Observable<number>; | |
chunk$?: ReplaySubject<XMLHttpRequest[]>; | |
onsave$?: Observable<FileDownload>; | |
status?: FileDownloadStatus; | |
existingInfoStored?: Object; | |
localFileUri?: string; | |
} | |
export enum FileDownloadStatus { | |
STARTED = 'started', | |
ENDED = 'ended', | |
SAVED = 'saved', | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment