Skip to content

Instantly share code, notes, and snippets.

@pedropapa
Created September 5, 2018 20:41
Show Gist options
  • Save pedropapa/dd05f3de8270ec1adb15183b06c3c749 to your computer and use it in GitHub Desktop.
Save pedropapa/dd05f3de8270ec1adb15183b06c3c749 to your computer and use it in GitHub Desktop.
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