Skip to content

Instantly share code, notes, and snippets.

@swarog
Created January 23, 2016 08:47
Show Gist options
  • Save swarog/3ee380f8ba103965c21c to your computer and use it in GitHub Desktop.
Save swarog/3ee380f8ba103965c21c to your computer and use it in GitHub Desktop.
Fix for appcache issue with playing audio in offline mode in the Chrom on Android.
/**
* AppCache audio tag caching fix main code
*/
(function () {
//document.addEventListener("DOMContentLoaded", function () {
hashCode = function (str) {
var hash = 0;
if (str.length == 0) return hash;
for (var i = 0; i < str.length; i++) {
char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
mediaObjectFix = function (mediaObjects) {
if(typeof globMediaObjects == 'undefined') {globMediaObjects = new Array()};
for (var i = 0; i < mediaObjects.length; i++) {
if (!mediaObjects[i].myId) {
mediaObjects[i].myId = Math.random();
globMediaObjects.push(mediaObjects[i]);//TODO: remove
}
(function (mediaObject) {
if(mediaObject.src.indexOf('filesystem:') === 0) {
return;
}
var observer = new MutationObserver(function (mutations) {
mutations[0].target.play = function(play) {
return function() {
if(this.src.indexOf('data:') !== 0 && (this.src.indexOf('filesystem:') !== 0 || this.src == 'filesystem://123.mp3')) {
var audio = this;
//TODO: Replace to real URL
this.paused = false;
setTimeout(function() { audio.play(); }, 200);
return true;
}
//if(this.paused == false) return;
var ret = play.apply(this, arguments);
return ret;
};
}(mutations[0].target.play);
/* mutations[0].target.load = function(load) {
return function () {
debugger;
var ret = load.apply(this, arguments);
return ret;
}
}(mutations[0].target.load);*/
mediaObjectFix(new Array(mutations[0].target)); //TODO: remove array
});
var observerConfig = {attributes: true, childList: false, characterData: false, subtree: false, attributeFilter: new Array('src')};
if (!mediaObject.myobserver) {
observer.observe(mediaObject, observerConfig); //TODO: seems this handled several times
mediaObject.myobserver = observer;
}
if(mediaObject.src == "" || mediaObject.src.indexOf('data:') === 0 || mediaObject.src == 'about:blank') {
return;
}
/* mediaObject.onplay = function(e) {
e.preventDefault();
debugger;
return false;
}*/
mediaObject.oldSrc = mediaObject.src;
mediaObject.myobserver.disconnect();
mediaObject.src = 'filesystem://123.mp3';
mediaObject.myobserver.observe(mediaObject, observerConfig);
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function () {
var tmpAnchor = document.createElement('A'); //Dirty hack for URL parsing
tmpAnchor.href = mediaObject.oldSrc;
var fileArr = tmpAnchor.pathname.split('/');
var fileName = fileArr.pop();
if(fileArr[0] == '') fileArr.shift(1); //TODO: Dirty hack, add checking for different file paths
if(fileArr.length > 0) {
var createFolder = function(path, folderPathArr) {
path = path + '/' + folderPathArr.shift(1);
fileSystem.root.getDirectory(path, {create: true, exclusive: false}, function(directoryEntry) {
if(folderPathArr.length > 0) {
createFolder(path, folderPathArr);
} else {
directoryEntry.getFile(fileName, {create: true}, function (fileEntry) {
var currFileEntry = fileEntry;
var currMediaObject = mediaObject;
fileEntry.createWriter(function (fileWriter) {
fileWriter.write(xhr.response);
currMediaObject.myobserver.disconnect();
currMediaObject.src = currFileEntry.toURL();
currMediaObject.myobserver.observe(currMediaObject, observerConfig);
currMediaObject.load();
});
});
}
},
function () {
debugger;
});
};
createFolder('', fileArr);
}
else {
fileSystem.root.getFile(fileName, {create: true}, function (fileEntry) {
var currFileEntry = fileEntry;
var currMediaObject = mediaObject;
fileEntry.createWriter(function (fileWriter) {
fileWriter.write(xhr.response);
currMediaObject.myobserver.disconnect();
currMediaObject.src = currFileEntry.toURL();
currMediaObject.myobserver.observe(currMediaObject, observerConfig);
currMediaObject.load();
});
});
}
}
xhr.open('GET', mediaObject.oldSrc);
xhr.send();
}(mediaObjects[i]));
}
}
var observer = new MutationObserver(function (mutations) {
var mediaObjectsForFix = new Array();
var domTreeTraversing = function (domNodes) {
for (var i = 0; i < domNodes.length; i++) {
if (domNodes[i].childNodes.length != 0) {
domTreeTraversing(domNodes[i].childNodes);
}
if (domNodes[i].tagName == "AUDIO") {
mediaObjectsForFix.push(domNodes[i]);
}
}
}
for (var i = 0; i < mutations.length; i++) {
var mutation = mutations[i];
if (mutation.addedNodes.length == 0) {
continue;
}
domTreeTraversing(mutation.addedNodes);
}
mediaObjectFix(mediaObjectsForFix);
});
var observerConfig = {attributes: false, childList: true, characterData: false, subtree: true};
var doFix = function () {
document.createElement = function(create) {
return function() {
var ret = create.apply(this, arguments);
if (ret.tagName.toLowerCase() === "audio") {
//ret.onloadstart = function() {debugger;}
mediaObjectFix(new Array(ret)); //TODO: remove Array
}
return ret;
};
}(document.createElement)
observer.observe(document, observerConfig);
var audioTags = document.getElementsByTagName('AUDIO');
mediaObjectFix(audioTags);
}
var fileSystem;
//TODO: add storage-space dynamic calculation, and extension
window.webkitRequestFileSystem(TEMPORARY, 1024 * 1024 * 300, function (fs) {
fileSystem = fs;
if (bowser.chrome && bowser.android) {
//if (bowser.chrome && bowser.android || true) {
window.addEventListener("offline", function (e) {
doFix();
}, false);
window.addEventListener("online", function (e) {
observer.disconnect();
}, false);
if (!navigator.onLine) {
doFix();
} else {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
//FIXME: Realise more explicit check for request error
if(this.status != 200) {
//if(true) {
doFix();
}
}
xhr.open('GET', '/?r=' + Math.random());
xhr.send();
}
}
});
//});
}());
/*!
* Bowser - a browser detector
* https://github.com/ded/bowser
* MIT License | (c) Dustin Diaz 2015
*/
!function (name, definition) {
if (typeof module != 'undefined' && module.exports) module.exports = definition()
else if (typeof define == 'function' && define.amd) define(definition)
else this[name] = definition()
}('bowser', function () {
/**
* See useragents.js for examples of navigator.userAgent
*/
var t = true
function detect(ua) {
function getFirstMatch(regex) {
var match = ua.match(regex);
return (match && match.length > 1 && match[1]) || '';
}
function getSecondMatch(regex) {
var match = ua.match(regex);
return (match && match.length > 1 && match[2]) || '';
}
var iosdevice = getFirstMatch(/(ipod|iphone|ipad)/i).toLowerCase()
, likeAndroid = /like android/i.test(ua)
, android = !likeAndroid && /android/i.test(ua)
, chromeBook = /CrOS/.test(ua)
, edgeVersion = getFirstMatch(/edge\/(\d+(\.\d+)?)/i)
, versionIdentifier = getFirstMatch(/version\/(\d+(\.\d+)?)/i)
, tablet = /tablet/i.test(ua)
, mobile = !tablet && /[^-]mobi/i.test(ua)
, result
if (/opera|opr/i.test(ua)) {
result = {
name: 'Opera'
, opera: t
, version: versionIdentifier || getFirstMatch(/(?:opera|opr)[\s\/](\d+(\.\d+)?)/i)
}
}
else if (/yabrowser/i.test(ua)) {
result = {
name: 'Yandex Browser'
, yandexbrowser: t
, version: versionIdentifier || getFirstMatch(/(?:yabrowser)[\s\/](\d+(\.\d+)?)/i)
}
}
else if (/windows phone/i.test(ua)) {
result = {
name: 'Windows Phone'
, windowsphone: t
}
if (edgeVersion) {
result.msedge = t
result.version = edgeVersion
}
else {
result.msie = t
result.version = getFirstMatch(/iemobile\/(\d+(\.\d+)?)/i)
}
}
else if (/msie|trident/i.test(ua)) {
result = {
name: 'Internet Explorer'
, msie: t
, version: getFirstMatch(/(?:msie |rv:)(\d+(\.\d+)?)/i)
}
} else if (chromeBook) {
result = {
name: 'Chrome'
, chromeBook: t
, chrome: t
, version: getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)
}
} else if (/chrome.+? edge/i.test(ua)) {
result = {
name: 'Microsoft Edge'
, msedge: t
, version: edgeVersion
}
}
else if (/chrome|crios|crmo/i.test(ua)) {
result = {
name: 'Chrome'
, chrome: t
, version: getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)
}
}
else if (iosdevice) {
result = {
name: iosdevice == 'iphone' ? 'iPhone' : iosdevice == 'ipad' ? 'iPad' : 'iPod'
}
// WTF: version is not part of user agent in web apps
if (versionIdentifier) {
result.version = versionIdentifier
}
}
else if (/sailfish/i.test(ua)) {
result = {
name: 'Sailfish'
, sailfish: t
, version: getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i)
}
}
else if (/seamonkey\//i.test(ua)) {
result = {
name: 'SeaMonkey'
, seamonkey: t
, version: getFirstMatch(/seamonkey\/(\d+(\.\d+)?)/i)
}
}
else if (/firefox|iceweasel/i.test(ua)) {
result = {
name: 'Firefox'
, firefox: t
, version: getFirstMatch(/(?:firefox|iceweasel)[ \/](\d+(\.\d+)?)/i)
}
if (/\((mobile|tablet);[^\)]*rv:[\d\.]+\)/i.test(ua)) {
result.firefoxos = t
}
}
else if (/silk/i.test(ua)) {
result = {
name: 'Amazon Silk'
, silk: t
, version: getFirstMatch(/silk\/(\d+(\.\d+)?)/i)
}
}
else if (android) {
result = {
name: 'Android'
, version: versionIdentifier
}
}
else if (/phantom/i.test(ua)) {
result = {
name: 'PhantomJS'
, phantom: t
, version: getFirstMatch(/phantomjs\/(\d+(\.\d+)?)/i)
}
}
else if (/blackberry|\bbb\d+/i.test(ua) || /rim\stablet/i.test(ua)) {
result = {
name: 'BlackBerry'
, blackberry: t
, version: versionIdentifier || getFirstMatch(/blackberry[\d]+\/(\d+(\.\d+)?)/i)
}
}
else if (/(web|hpw)os/i.test(ua)) {
result = {
name: 'WebOS'
, webos: t
, version: versionIdentifier || getFirstMatch(/w(?:eb)?osbrowser\/(\d+(\.\d+)?)/i)
};
/touchpad\//i.test(ua) && (result.touchpad = t)
}
else if (/bada/i.test(ua)) {
result = {
name: 'Bada'
, bada: t
, version: getFirstMatch(/dolfin\/(\d+(\.\d+)?)/i)
};
}
else if (/tizen/i.test(ua)) {
result = {
name: 'Tizen'
, tizen: t
, version: getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.\d+)?)/i) || versionIdentifier
};
}
else if (/safari/i.test(ua)) {
result = {
name: 'Safari'
, safari: t
, version: versionIdentifier
}
}
else {
result = {
name: getFirstMatch(/^(.*)\/(.*) /),
version: getSecondMatch(/^(.*)\/(.*) /)
};
}
// set webkit or gecko flag for browsers based on these engines
if (!result.msedge && /(apple)?webkit/i.test(ua)) {
result.name = result.name || "Webkit"
result.webkit = t
if (!result.version && versionIdentifier) {
result.version = versionIdentifier
}
} else if (!result.opera && /gecko\//i.test(ua)) {
result.name = result.name || "Gecko"
result.gecko = t
result.version = result.version || getFirstMatch(/gecko\/(\d+(\.\d+)?)/i)
}
// set OS flags for platforms that have multiple browsers
if (!result.msedge && (android || result.silk)) {
result.android = t
} else if (iosdevice) {
result[iosdevice] = t
result.ios = t
}
// OS version extraction
var osVersion = '';
if (result.windowsphone) {
osVersion = getFirstMatch(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i);
} else if (iosdevice) {
osVersion = getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i);
osVersion = osVersion.replace(/[_\s]/g, '.');
} else if (android) {
osVersion = getFirstMatch(/android[ \/-](\d+(\.\d+)*)/i);
} else if (result.webos) {
osVersion = getFirstMatch(/(?:web|hpw)os\/(\d+(\.\d+)*)/i);
} else if (result.blackberry) {
osVersion = getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i);
} else if (result.bada) {
osVersion = getFirstMatch(/bada\/(\d+(\.\d+)*)/i);
} else if (result.tizen) {
osVersion = getFirstMatch(/tizen[\/\s](\d+(\.\d+)*)/i);
}
if (osVersion) {
result.osversion = osVersion;
}
// device type extraction
var osMajorVersion = osVersion.split('.')[0];
if (tablet || iosdevice == 'ipad' || (android && (osMajorVersion == 3 || (osMajorVersion == 4 && !mobile))) || result.silk) {
result.tablet = t
} else if (mobile || iosdevice == 'iphone' || iosdevice == 'ipod' || android || result.blackberry || result.webos || result.bada) {
result.mobile = t
}
// Graded Browser Support
// http://developer.yahoo.com/yui/articles/gbs
if (result.msedge ||
(result.msie && result.version >= 10) ||
(result.yandexbrowser && result.version >= 15) ||
(result.chrome && result.version >= 20) ||
(result.firefox && result.version >= 20.0) ||
(result.safari && result.version >= 6) ||
(result.opera && result.version >= 10.0) ||
(result.ios && result.osversion && result.osversion.split(".")[0] >= 6) ||
(result.blackberry && result.version >= 10.1)
) {
result.a = t;
}
else if ((result.msie && result.version < 10) ||
(result.chrome && result.version < 20) ||
(result.firefox && result.version < 20.0) ||
(result.safari && result.version < 6) ||
(result.opera && result.version < 10.0) ||
(result.ios && result.osversion && result.osversion.split(".")[0] < 6)
) {
result.c = t
} else result.x = t
return result
}
var bowser = detect(typeof navigator !== 'undefined' ? navigator.userAgent : '')
bowser.test = function (browserList) {
for (var i = 0; i < browserList.length; ++i) {
var browserItem = browserList[i];
if (typeof browserItem === 'string') {
if (browserItem in bowser) {
return true;
}
}
}
return false;
}
/*
* Set our detect method to the main bowser object so we can
* reuse it to test other user agents.
* This is needed to implement future tests.
*/
bowser._detect = detect;
return bowser
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment