Posts by Scott Witte

    I have no personal experience using the below. I just did some research a while ago for a possible project that hasn't come through yet. You probably want a Mobile Device Manager that can provide kiosk mode and remote updates.

    ArborXR and ManageXR seem capable. Both have a free trial but require a subscription to use after that.

    Look into Progressive Web Apps as a way to strip away the browser interface and just display your tour. That's on my to do list. It looks very promising.

    Hope this helps.

    Tim -- I looked into this several years ago and gave up, especially for Quest headsets. If you search for Cordova in the forums here you will find several discussions. It sounds like you already did a Google search for "Cordova" WebXR and found the couple of GitHub pages that haven't been updated in years.

    If your primary purpose is to run your tours offline, a local server on the device is the easiest way to go. I recently did that for a museum client. More than that, they wanted the tablets locked down in kiosk mode so only the tour loaded onto the tablets was available to users -- no other apps or web surfing. Android proved the easiest for that. Just search for local host in the Google Play Store. You should find several options. The setup was straightforward. And if you want Kiosk mode -- running only your tour(s) and removing all aspects of the browser outside the page -- you will find that easily as well. Understanding that setup took a bit longer but still far easier than building an app in Cordova.

    For the Quest headset I had to sideload the local server but I can run tours locally on my Quest 2. I suspect it works just as well on the Quest 3. Consider using the Wolvic browser, which I believe can be used as more of a standalone app. You will need KRPano v1.21+ to work properly.

    There are several kiosk mode for Quest products available as well, if you need that, but they require subscriptions, from what I remember.

    At least that has been my experience.

    Recently, zoomtospotsextent() has stopped working on my site with Google Maps. I know it worked a few months ago and for a couple of years before that. I didn't change anything in the code that I can remember, so what might it be?

    The first time zoomtospotsextent() is called it results in the error:

    function oa(){if(h)if(0==H("zoomtospotsextent"))v=C=!1,K=!0;else{v=!1;var a,d,b,;d=f.length;if(!(1>d))if(1==d)b=f[0].internalObject,na(,b.lng,t);else{var g=new google.maps.LatLngBounds;for(a=0;a<d;a++)b=f[a].internalObject,g.extend(new google.maps.LatLng(,b.lng));h.fitBounds(g)}}else v=C=!1,K=!0}

    When called after that I get the warning:

    WARNING: Unknown action: zoomtospotsextent

    Other than for zoomtospotsextent(), the map continues to work fine.

    This was originally programmed using v1.20.9 in 2021. I tried substituting the v 1.21 googlemaps.js. Still didn't work. I didn't get the first error message but I did get the warnings about an unknown action.

    Several updates and in the end I seem to have found a solution, but not a great on.

    I don't think the preloading idea would work. From what I can tell there is no way to know if the preloading has finished. As I recall I could do that using javascript but that would make this whole issue more complicated.

    Klaus - I wonder if adding a sound[name].onloaded event might be worthwhile. Perhaps consider having stopsound() cancel the downloading of a sound file if it hasn't completed, yet.

    The solution was to play another sound in the same-named sound object. So, while sound[tour] is still downloading playsound(tour, "silence.mp3") prevents the downloading file from playing. I don't think the downloading is actually stopped because a little later an error gets thrown, "ERROR: Soundinterface Load Error: Decoding audio data failed." If that is correct, the old downloading file will continue to hog bandwidth that won't be available to download a new audio file. A way to actually cancel the download would be better. Forcing a user on a slow connection to sit idle while an unwanted download finishes before they can play a different sound file is not a good experience.

    Silence.mp3 is a one-second mp3 of silence.

    Originally I said that I tried the above and it didn't work. That was when I called an action in my soundplayer plugin to play silence.mp3. Although it includes the same playsound() action, that didn't work. Just calling playsound() by itself did, although apparently only by throwing an error.

    BTW, you can tell if the file is still downloading by checking sound[name].position. If it isn't > 0 it hasn't started playing because it hasn't finished downloading.

    Why has no developer had this problem before? I think it is because we all have fast systems with fast internet so we wouldn't notice it. Not everyone is so lucky.

    When you play a sound file, for instance, a vocal description when popping up an image (eg. playsound(tour, "something.mp3"), it doesn't actually start playing until the file is fully loaded into cache. What if the viewer leaves the event/popup that called the sound file before it starts playing? You don't want to have it start a second or two later. stopsound(tour) doesn't work since the sound hasn't started. destroysound(tour) causes an error since the sound doesn't exist, yet, apparently. I tried to supersede the unplayed sound with another using the same id, such as playsound(tour, "silence.mp3") but that didn't work.

    So, how do you stop a sound before it starts paying?

    Note: streamsound() may be an option but unless there is a solution to the above, you have to conclude that playsound() can almost never be safely used. Preloading all the sound files isn't a great solution. Say you have 50 voice-over files and which is played first is entirely random. If the viewer picks one that hasn't been preloaded the wait before starting could be even worse, by my understanding.

    What is the actual problem?

    Mostly that this isn't documented anywhere that I could find.

    The normal practice is for CSS to be in stylesheets (internal or external) vs inline. It took me forever to figure out that in this case, it had to be inline. Being in a stylesheet didn't matter. I don't think that is intuitive so it would be useful for you to document it. Better still, IMHO, would be for it to not matter where height and width are set if that is possible.

    I feel it is good to document anything needed to make something work, including something like viewport-fit. That way, if something isn't working for someone they can more easily figure it out.

    BTW, your figuring out how to get around limitations like the lack of real fullscreen on the iphone is hugely appreciated.

    Totally weird. Unless you set height and width to 100% in a style element inside the pano div tag (inline), iphone_fullscreen_swipe won't work. If you set these in a stylesheet instead, the div will size as expected but iphone_fullscreen_swipe doesn't work. I can't see anything in the javascript that would cause this so I suspect it has to do with the oniphonefullscreen event not firing.

    Also, I found that the viewport meta tag must include "viewport-fit=cover" although that makes a little more sense.


    Hi Klaus,

    Thanks for the clarification. Adding a basepath parameter to embedpano worked. That was definitely unclear to me before. There is no reference to this in the URL docs.

    I foresee problems going forward. Beginning with v1.20.11 we are getting rid of flash entirely. Therefore the flash player, including its path, is no longer defined. Therefore %VIEWER%, %ROOT%, and %SWFPATH% no longer have a value. But %VIEWER% etc. is used everywhere in KRPano. That obviously can lead to problems.

    Perhaps with the next iteration you could use the path to tour.js to define %VIEWER% etc?


    Hi Klaus,

    %VIEWER% is being interpreted incorrectly in v1.20.11. In my file structure it results in ./ instead of ../

    My file structure is a little different than the default:

    When I open tour.html I get the error "./plugins/webvr.xml - loading failed! (0)"

    If I hardcode ../ instead of %VIEWER%/ it works fine. This wasn't an issue in any previous versions.


    Anyway, why not use Teams/Zoom and their "share screen" feature ?

    My experience using Zoom was pretty bad. The pano motion is very jerky, for instance.

    Anyway, for my purpose I want the user to have full control of the view when they want, just as if they were there in person. But I also want the presenter to have the ability to force the view or force a popup so everyone is looking at the same thing at the same time. I also want to force everyone to switch to the next pano at the same time, again, like on a real tour.

    That sounds server-side to me.

    This is an important capability that I anticipate needing before long as well. I thought Hamid's plugin looked pretty good, although I haven't tried it, so it is a little distressing that there are shortcomings which he hasn't addressed.

    I know from experience that adding this capability or creating a plugin like this isn't trivial. For someone to start again from scratch would be an unfortunate waste.

    I suspect Google Street View is a bigger influence here than Matterport, not that it matters. Interesting idea, though.

    If all you want is for the up and down keys to load the next and previous pano, you can edit the skin_keydown_event section in vtourskin.xml adding:

    if(keycode == 38, skin_nextscene_loop(+1) );

    But that won't give you the same behavior as Matterport or Street View where the up arrow takes you to the scene who's navigation arrow is closest to your center of view. That is especially obvious when you have three or more navigation hotspots in a scene.

    To accomplish Street View navigation the up arrow would need to call an action that finds the navigation hotspot with an ath that most closely matches your current view hlookat. I would then call the click event for that hotspot.

    Some caveats: You want to programmatically exclude any hotspots that aren't for navigation in the comparison. You also need to do some math to account for the 360 fov. For instance, if your current hlookat is 179 and there is a navigation hotspot with an ath of -179, those are are only 2 degrees apart, not 358. I believe you use the mod math function to make an accurate comparison.

    It would be the opposite with the down arrow where you want the navigation hotspot farthest away from you current hlookat.

    Hope that helps.


    A container with bgalpha > 0 and bgcapture=true will block mouse button events from layers below. Is there a way to do the same with mouseover events?

    Sooooo.... Never mind. Further tests show that a container layer using bgcapture and fully covering another layer will prevent mouseover events from reaching the underlying layer. But if even one pixel with an alpha >0 peaks out from under it is enough to fire the onover event. That totally makes sense. Now I just have to find my stray pixels. *unsure*

    (I wish there were a way to just delete dumb posts.....)