I am working on a Chrome app that switches between medias shown in a webview tag. The medias must be updated when they change on my web server and it must keep playing when the internet or the server go down. My solution was to play the media from a programmatically managed local cache.
I programmed the Chrome app to download the content with webkitRequestFileSystem into persistent local storage and I try to access the files from there. The content ends up with URLs like this filesystem:chrome-extension://kbjjicmijilgpdpkicpbnceofdpfbjcb/persistent/my_file.html.
No matter what I do, I cannot access the local files. Neither the webview nor navigating directly to the file displays it in Chrome. It only shows a ERR_FILE_NOT_FOUND error.
How should I go about showing this local content in my webview?
Here is my saveAsset() code (strongly inspired by this answer) :
function saveAsset(fs, url, filename, callback, failCallback) {
// Set callback when not defined
if (!callback) {
callback = function(cached_url) {
console.log('download ok: ' + cached_url);
};
}
if (!failCallback) {
failCallback = function() {
console.log('download failed');
};
}
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.addEventListener('load', function() {
fs.root.getFile(filename, {create: true, exclusive: false}, function(fileEntry) {
fileEntry.createWriter(function(writer) {
writer.onwrite = function(e) {
callback(fileEntry.toURL());
};
writer.onerror = failCallback;
console.log(xhr.response);
var blob = new Blob([xhr.response], {type: ''});
writer.write(blob);
}, failCallback);
}, failCallback);
});
xhr.addEventListener('error', failCallback);
xhr.send();
}
And this is the code that sets the src attribute of the webview to that URL.
window.webkitRequestFileSystem(window.PERSISTENT, 1024*1024, function (fs) {
saveAsset(fs, url, filename, function(localUrl) {
console.log(localUrl);
$(contentSelector).attr("src", localUrl);
});
});
I never managed make the webview work with local content, but switching to an iframe does work perfectly. I have both cached and uncached content so it makes some confusing code, but I fixed my issue by using both a webview and an iframe and hiding the one which is not in use.
PS. For anyone trying to do this, the webview won't load its content correctly if it is hidden with display:none . I had to move it outside of the screen instead.
Related
I am currently tying our audio player with mediaSession.
Everything is working as it should, when I hit play and update navigator.mediaSession.metadata, it's properly displayed in the notification on desktop and mobile.
But after I reload the page and hit play, the notification has always default values (website URL as a title and link rel="icon" for the artwork). This only happens after I reload the website. If I close it and open again the notification is working properly again.
Here's how it's done:
//...
initialConfiguration: {
title: 'Initial Title',
artist: 'Initial Artist',
album: '',
artwork: [
{ src: "initial/artwork/url.jpg", sizes: "512x512", type: "image/jpg" },
]
},
currentMetadata: null,
setMediaSessionMetaData: function(){
let self = this;
if ('mediaSession' in navigator) {
if( !self.currentMetadata ){
self.currentMetadata = new MediaMetadata(self.initialConfiguration);
}else{
// Update existing metadata
self.currentMetadata.title = "New Title";
self.currentMetadata.artist = "New Artist";
self.currentMetadata.artwork = [
{ src: "new/artwork/url.jpg", sizes: '512x512', type: "image/jpg" },
];
}
navigator.mediaSession.metadata = self.currentMetadata;
}
},
//...
This function works perfectly fine on first page load, when I hit play for the first time it loads the initialConfiguration and if I call the function again the title and artwork gets updated. But after reload, the notification has always default values ignoring my configuration.
Is there a bug in mediaSession, I didn't find anything regarding this issue on mediaSession github page (https://github.com/w3c/mediasession/issues) and searching this issue gives me zero results.
After Searching for days and not finding anything....
I discovered a workaround. It has to do with the performance.navigation.type when you first open a page it is set to 0 and after a refresh it gets set to 1. I was curious if this had any effect on the mediasession so I figured out how to "Hard Refresh" the page by using window.open(window.location.href, "_self");, because apparently that generates a "Hard Refresh". After that the mediasession works again. so what I did was setup an onload function that checks to see if performance.navigation.type == 1 and if so then do a "Hard Refresh"
window.onload = function() {
if (performance.navigation.type != 0) {
window.open(window.location.href, "_self");
}
};
Now when the user refresh's it will do a "Hard Refresh".
Also make sure to move the window. Onload line to the end of the javascript file or after the initial function
I am new to RTCPeerConnection (WEbRTC), so please bear with me.
So far I am able to get to the point where I can replace tracks on the run by switching camera or screen sharing in my app. But I noticed it in 2 browser tabs that newly replaced track stream is captured in partner/remote peer only, not on initiator's tab. It just keep showing old stream even though stream has been replaced.
It should've been nice if initiator can also see what he/she is sharing. I tried but no luck so far. Looking for some assistance.
My code looks like:
function screenShare(){
(async () => {
try {
await navigator.mediaDevices.getDisplayMedia(
{
cursor: true
}).then(stream => {
// localStream = stream;
let videoTrack = stream.getVideoTracks()[0];
var sender = senders.find(function(s) {
return s.track.kind == videoTrack.kind;
});
sender.replaceTrack(videoTrack);
videoTrack.onended = function(){
sender.replaceTrack(localStream.getTracks()[1]);
}
});
} catch (err) {
console.log('(async () =>: ' + err);
}
})();
}
Thanks in advance.
By design replaceTrack replaces the stream on the RTCPeerConnection. This does not affect the local video object. Reset the srcObject on the local video element to change it.
I am trying to build app with WebView and click/tap events on URL-s inside WebView. Solution below opens external browser and URL but it loads same url content in webview as well. Is there a way to prevent loading new url inside webview?
Here is my code sample.
function onWebViewLoaded(webargs) {
const page = webargs.object.page;
const vm = page.bindingContext;
const webview = webargs.object;
webview.on(webViewModule.WebView.loadFinishedEvent, (args) => {
let message = "Loading in progress....";
if (!args.error) {
message = `WebView loading finished with url: ${args.url}`;
} else {
message = `Error received ${args.url} : ${args.error}`;
}
if (args.url.indexOf('http://') === 0) {
// Stop the loading url first... but how..
// Open URL in external default browser
utilityModule.openUrl(args.url);
}
});
}
I have tried with setting a flag isUserInteractionEnabled="false" added to my xml view but then all interactions are disabled. Does someone knows how to do this?
Try with loadStartedEvent
webview.on(webViewModule.WebView.loadStartedEvent, (args) => {
if(!args.url.includes("whatever.net")){
webview.stopLoading();
utilsModule.openUrl(args.url);
}
});
This way we catch the loadStartedEvent instead of loadFinishedEvent (where the url is already loaded), we check the url if we want to filter depending on it, and here we can webview.stopLoading() for stop the page load.
I have a jQuery mobile panel which slides in from the side, it works great.
But lets say you have a login page, that redirects to a main page with a panel. Now if the user opens the panel, and then clicks the back button, he expects the panel to close. But instead the browser navigates back to the login page.
I´ve tried adding something to the url:
window.location.hash = "panelOpen";
But that just messes up the jQuery mobile history state pattern. I´ve also tried to listen to the navigate event, and prevent it if a panel is open:
$(window).on('navigate', function (e, hans) {
var panels = $('[data-role="panel"].ui-panel-open');
if (panels&&panels.length>0) {
e.preventDefault();
e.stopPropagation();
$('#' + panels[0].id).panel('close');
return false;
}
});
This kind of works, except that the url is changed, and I cannot grab the event that changes the url. Furthermore, it also messes up the jQuery mobile history pattern.
So how does people achieve this expected 'app-like' behaviour with a jQuery mobile panel; open panel > history back > close panel. And thats it.
Thanks alot!
Updated
Instead of retrieving current URL from jQuery Mobile's history, It is safer to retrieve it from hashchange event event.originalEvent.newURL and then pass it to popstate event to be replaceState() with that URL.
Instead of listening to navigate, listen to popstate which fires before. The trick here is manipulate both browser's history and jQuery Mobile's history by replaceState() and reload same page without transition.
var newUrl;
$(window).on("hashchange", function (e) {
/* retrieve URL */
newUrl = e.originalEvent.newURL;
}).on("popstate", function (e) {
var direction = e.historyState.direction == "back" ? true : false,
activePanel = $(".ui-panel-open").length > 0 ? true : false,
url = newUrl,
title = document.title;
if (direction && activePanel) {
$(".ui-panel-open").panel("close");
$(".ui-header .ui-btn-active").removeClass("ui-btn-active");
/* reload same page to maintain jQM's history */
$.mobile.pageContainer.pagecontainer("change", url, {
allowSamePageTransition: true
});
/* replace state to maintain browsers history */
window.history.replaceState({}, title, url);
/* prevent navigating into history */
return false;
}
});
This part is meant to maintain same transition used previously as transition is set to none when reloading same page.
$(document).on("pagebeforechange", function (e, data) {
if (data.options && data.options.allowSamePageTransition) {
data.options.transition = "none";
} else {
data.options.transition = $.mobile.defaultPageTransition;
}
});
Demo - Code
I am a little bit late on the party, but i had recently the same requirements and i would like to share how i did it. So, i extended the requirement in the original question to Panels, Popups and Pages:
...an expected 'app-like' behaviour, history back > close
whaterver is open. And thats it.
In .on("panelopen"), .on("popupafteropen") and .on("pagecontainershow") i simply add another entry to the window history, by using the HTML5 API (https://developer.mozilla.org/en-US/docs/Web/API/History_API) (I believe there is no need to use the JQM navigate browser quirks for that):
window.history.pushState({}, window.document.title, window.location.href);
After that, i'm using more or less Omar's function to intercept the popstate event:
$(window).on("popstate", function (e) {
var pageId = $(":mobile-pagecontainer").pagecontainer("getActivePage").prop("id");
var pageOpen = (pageId != "page-home");
var panelOpen = $(".ui-panel-open").length > 0;
var popupOpen = $(".ui-popup-active").length > 0;
if(pageOpen) {
$.mobile.pageContainer.pagecontainer("change", "#page-home", {reverse: true});
return false;
}
if(panelOpen) {
$(".ui-panel-open").panel("close");
return false;
}
if(popupOpen) {
$(".ui-popup-active .ui-popup").popup("close")
return false;
}
});
As you see, the is just only one level to the home-page, but this can be easily extended by using JQM history implementation to get the previous page:
var activeId = $.mobile.navigate.history.activeIndex;
var jqmHistory = $.mobile.navigate.history.stack; // array of pages
and use pagecontainer to change to the active entry - 1.
As last note, this works well also by completely disabling the built-in JQM Ajax navigation system:
/* Completely disable navigation for mobile app */
$.mobile.ajaxEnabled = false;
$.mobile.loadingMessage = false;
$.mobile.pushStateEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.changePage.defaults.changeHash = false;
$.mobile.popup.prototype.options.history = false;
(Tested in Browser, on real Android and iOS devices)
I'm facing a problem using this 2 PhoneGap plugins: "BarcodeScanner" & "ChildBrowser" (inside an iOS app, with XCode 4 & PhoneGap 2.0).
I've a button "Scan" on my app UI. When the user clic on this button, the barcode scanner is launched.
So, in the Success function of the barcode scanner callback, I need to open the recovered URL from the scan in a new Childbrowser window (inner the app).
But the new Childbrowser window is never been opened, while the console displays "Opening Url : http://fr.wikipedia.org/" (for example).
Here is my JS part of code:
$("#btnStartScan").click(function() {
var scanBarcode = window.plugins.barcodeScanner.scan(
function(result) {
if (!result.cancelled){
openUrl(result.text);
}
},
function(error) {
navigator.notification.alert("scanning failed: " + error);
});
});
function openUrl(url)
{
try {
var root = this;
var cb = window.plugins.childBrowser;
if(cb != null) {
cb.showWebPage(url);
}
else{
alert("childbrowser is null");
}
}
catch (err) {
alert(err);
}
}
And all works fine if I call my openURL() function inside a Confirm alert callback for example, like this:
if (!result.cancelled){
navigator.notification.confirm("Confirm?",
function (b) {
if (b === 1) {
openUrl(result.text);
}
},
'Test',
'Yes, No');
}
But I need to launch the ChildBrowser window directly after a scan, without any confirm alert etc.
Does anybody know how to solve this please?
I also have this same problem.
Solve it by set timeout.
var scanBarcode = window.plugins.barcodeScanner.scan(
function(result) {
if (!result.cancelled){
setTimeout(function(){ openUrl(result.text); },500);
}
},
function(error) {
navigator.notification.alert("scanning failed: " + error);
});
I'm running into the exact same problem.
My application also has another mechanism to show a webpage besides the barcode reader and when I do that action I can see that the barcode-related page HAD loaded, but it never was shown.
In ChildBrowserViewController.m, I'm looking at the last line of loadURL() which is webView.hidden = NO; and I'm thinking that the child browser is set visible after we barcode but something about the barcode reader window caused the child browser to get set to the wrong z-order, but I'm not familiar enough with the sdk to know how to test that or try to bring it to the front.
Hope this helps target a potential area.