google / marzipano

A 360° media viewer for the modern web.
http://www.marzipano.net
Apache License 2.0
2k stars 999 forks source link

Request: levelChange event #188

Closed mjau-mjau closed 6 years ago

mjau-mjau commented 6 years ago

I have spent quite some time researching the docs, debugging outputs, posting (no reply :( ), trying to find a reliable method to find out when Marzipano triggers a level change event.

My goal is to be able to display loading indicators (and more) when/if level changes and if textures start loading. The textureLoad event is kinda unusable alone if there is no way to know if level changes and textures starts loading in the first place. Surely a levelChange event or currentLevel property would be useful for extended interfaces?

Is there no existing method to get current level or detect level change?

samuelgirardin commented 6 years ago

about level, did you see this : RectilinearView.prototype.selectLevel = function(levelList){ .. return level according height and fov).

mjau-mjau commented 6 years ago

@samuelgirardin Thanks, but I can only see that this function is used to manually SET the level to be used to render the view. http://www.marzipano.net/reference/RectilinearView.html#selectLevel

Select the level that should be used to render the view.

samuelgirardin commented 6 years ago

in fact no, this method is always used when zooming , put some console.log : `for (var i = 0; i < levelList.length; i++) {

          if (this._coverFactor * levelList[i].height() >= this._requiredPixels) {

            console.log( levelList[i]) ; 

                return levelList[i];

            }
        }`

you will see sthg like that CubeLevel {_fallbackOnly: false, _size: 1024, _tileSize: 512} rectilinearView.js:817 CubeLevel {_fallbackOnly: false, _size: 1024, _tileSize: 512} rectilinearView.js:817 CubeLevel {_fallbackOnly: false, _size: 1024, _tileSize: 512} rectilinearView.js:817 CubeLevel {_fallbackOnly: false, _size: 1024, _tileSize: 512} rectilinearView.js:817 CubeLevel {_fallbackOnly: false, _size: 1024, _tileSize: 512} rectilinearView.js:817 CubeLevel {_fallbackOnly: false, _size: 2048, _tileSize: 512} rectilinearView.js:817 CubeLevel {_fallbackOnly: false, _size: 2048, _tileSize: 512} rectilinearView.js:817 CubeLevel {_fallbackOnly: false, _size: 2048, _tileSize: 512}

samuelgirardin commented 6 years ago

you can see here the switch between level 1024 and 2048

mjau-mjau commented 6 years ago

Thanks. I am actually using single-file 'EquirectGeometry' sources, two levels (two files), and I'm trying to figure out what loads when even before there is any zoom. Depending on screen, first level may be preloaded, while second level may be loading, or not even required at all. To determine this, regardless of any zoom (although that could trigger a new level), I need to detect current target level. Do you have code snippet example of selectLevel in action? It still seems a bit vague for the requirement, is not an event, and I need to feed it with some data?

In the meantime, I have had some progress with textureLoad, which seems to return an object with level data loaded. For EquirectGeometry sources, it returns z-index of the level and src width amongst other things.

textureStore.addEventListener('textureLoad', function(a, b) {
  // console.log(a); // just returns 'textureLoad' string for some reason
  console.dir(b); // returns a level object. This parameter is not mentioned in docs :/
});
tjgq commented 6 years ago

Given the current API, the best bet to detect whether textures are loading is the TextureStore#query method. The View#selectLevel method is only meant to be used internally.

I agree it makes sense to add the TextureStore#textureStartLoad and Layer#levelChange events.

Although not documented, all event handlers are called with the event name as the first argument. In the case of TextureStore#textureLoad, the second parameter is (as documented) the tile the event pertains to.

tjgq commented 6 years ago

I've added a Stage#renderComplete event which should make loading indicators a lot easier to implement.

The usage looks like this:

viewer.stage().addEventListener('renderComplete', function(eventName, complete) {
  // show or hide loading indicator depending on the value of `complete`
});

The API may change in the future, but for now complete is just a boolean (true = loading complete, false = loading in progress).

I will add an example to the demos soon.

mjau-mjau commented 6 years ago

Thanks @tjgq! I am very much looking forward to try new renderComplete and textureStartLoad events to achieve desired interface behaviors. Since they are not yet part of a release or NPM, I am personally a unsure how to compile github src for testing. In the meantime, I am using a textureLoad workaround for a beta version, but will test new events soon.

tjgq commented 6 years ago

You can follow the instructions under "Developer guide" in README.md if you'd like to compile a release for testing.

mjau-mjau commented 5 years ago

Hi. I have been looking into Stage#renderComplete event, and it does the job. A few observations:

1. It triggers fast. Perhaps as frequent as viewChange event? I can see it has to trigger frequently, since new textures may need to load after view change. I was however expecting it to perhaps only trigger when there was a state change to the complete value, but perhaps that is not logical to implement.

Can add our own logic for that of course, but I'm cautious about including events that trigger unnecessarily. Stage#renderComplete may not be productive for single-file equirect sources, since we are only expecting a single load.

2. The event triggers on window blur, even after viewer.destroyAllScenes(). I noticed this because I have a popup mechanism, and the event persists even after the popup is closed and all scenes are destroyed. Harmless, but I'm a bit paranoid about events leaking once I have destroyed all scenes and closed our panorama modal.

For my panorama 'popup' app, I am creating a persistent instance of Marzipano.Viewer that remains in memory (also after popup close). Then I just create/destroy scenes on popup open/close, to avoid having to recreate the Viewer instance each time, which includes a lot of controls and custom interface events. I'm still evaluating if this is a good idea, but apart from hogging some additional memory I assume, I can't see why not.

tjgq commented 5 years ago

The renderComplete event triggers whenever the stage renders, full stop. There's a number of situations that cause the stage to render (changes to the view, effects, fixed level, or loading/unloading of textures), but the render rate is capped by requestAnimationFrame (typically 60 fps).

Note that firing the event is very inexpensive compared to the render that has just occurred. If you only care about changes to the complete parameter, it should be cheap to check for it on the event handler. You could also unregister the handler if you only expect complete to change once.

Once you call Viewer#destroyAllScenes, I would expect a couple more render events to draw a blank stage, and maybe react to texture unloading (the latter is technically unnecessary). However, an empty stage should not keep firing renderComplete indefinitely; if you're seeing repeated events after you empty the stage, that definitely looks like a bug, and I'd be interested in looking at a minimal repro. (For what it's worth, I tried calling Viewer#destroyAllScenes in a couple of demos and the event stops firing.)