@mezzo-forte/geoxp
v1.3.12
Published
Mezzo Forte Geo Experience
Downloads
10
Readme
Mezzo Forte GeoXp
2021
Description
Mezzo Forte GeoXp is a client side, event based js library that manages all the core background features of a geolocated audio tour.
It maps audio contents to geographical positions, and automatically reproduces them based on configuration rules.
It’s meant to be used inside any front-end interface, regardless of the js framework.
An API and methods documentation page is available at this link.
The example app (code is in /example-app
directory) is published here.
It’s made of three modules.
Geo
Manages all geolocation logic. It’s based on geolocation API, but it can be used with any external geolocation provider.
Audio
Manages all audio playback logic. It relies on Howler JS library.
Experience
It’s the real core of the package, defines automatic actions to be done as a consequence of geolocation updates.
Install
npm install @mezzo-forte/geoxp
Contents
Key concepts
Geo key concepts
Positions
A GeoXp position is a circular area defined by two geographical coordinates ({lat, lon}
) a radius and a deadband.
The inner radius defines the inside position status.
(radius + deadband) defines an outer radius that serves hysteresis purposes to avoid abrupt status changes.
Geolocation providers
GeoXp default geolocation provider is the Web Geolocation API. Some framework or browser will not support Geolocation API, or have better ways to get the device location. It’s possible to use GeoXp with external geolocation providers (eg: Capacitor or native geolocation systems).
Minimum accuracy
Every geographical data, coming from the geolocation system, that does not fulfill accuracy requrements will be ignored.
Audio key concepts
Audio content
An audio content can be any audio file, reachable through an URL. GeoXp audio player is based on Howler.js (see here for suggested formats).
Experience key concepts
Spots
A GeoXp spot is the core entity of an experience and represents a relation between a position and an audio content. For example, if you want the user to listen the file audio_1.mp3
in the position position_A
, it will be necessary to create a new spot that associate the audio_1
content to the position_A
geographical coordinates (in the Usage section we describe in detail how to make this configuration).
This is to say, more than one spot can be linked to a certain position, the same audio content can be linked to multiple positions.
Behavior
The event behavior for a GeoXp spot is designed as follows:
User enters the position circle (its inside area), spot becomes active, associated audio content is played. User leaves the position inside area, but he’s still inside the deadband, the audio content is still playing. User leaves the deadband, spot becomes outgoing, the audio content fades out and stops.
Patterns
A list of spots is called a pattern. Patterns define the overall behavior of its spots. Multiple patterns could be active at any time, providing multiple simultaneous experiences (eg: one pattern defines what speeches to play in certain positions, one pattern defines background audio effects to play alongside the speeches, using the same positions). Patterns are separate entities that don’t talk to each other, spots order and content overlap management are independent between patterns.
Spots order
GeoXP provides limited content queue management. This can be achieved using the spot “after” and “notAfter” properties. If after is defined, GeoXp will not reproduce a certain spot content unless the after spot has already been played. If notAfter is defined, GeoXp will not reproduce a certain spot content if the notAfter spot has already been played.
Content replay
When content starts playing, a spot becomes visited
.
When the user reenters a visited spot, geoXp will not play its content. It will throw a notification instead, to let the user choose what to do.
This behavior can be overridden using the pattern replay
option. In this case, when the user reenters a visited spot, its content replays as usual.
See Spot content replay for details.
Cookies
As default behavior, when GeoXp instance is reloaded (eg: page refresh) exprience patterns memory of visited spots is cleared. This can be avoided enabling cookies for patterns in configuration, by appending a cookies
property to experience options.
When activated, a cookie for each pattern is updated every time a new spot is visited.
This cookie is deleted (to let the experience restart) when:
- by default, when all spots in a pattern are visited (
cookies.deleteOnCompletion
). - when a specific spot, flagged with the “last” option is activated (
cookies.deleteOnLastSpot
). - manually, when
geoXp.clearCookies()
is called. - manually, when geoXp instance is destroyed (
geoXp.destroy()
).
Please note that deleteOnCompletion
overrides deleteOnLastSpot
.
The cookie
property of a pattern
can be set to true
to use all the default options, or to an object with specific values See here for more details on configuration options
IMPORTANT Cookies have a standard lifespan of 5 minutes, which can be overridden with
cookies.expiration
. Please note that when cookies expire they are deleted, but no change is made runtime to the current geoXp visited spots. To let the experience to restart, a reload or page refresh is needed.
Content overlap
When the user is actually inside multiple spots at the same time (locations are overlapping, multiple spots are linked to the same location), as default behavior GeoXp will play one content at a time, with no overlapping. When the first audio finishes, the other starts. This can be overridden using the pattern “overlap” configuration option.
Manual mode
Sometimes geolocation data could be bad for unpredictable reasons, nerby buildings or trees could block part of the satellites communications, electromagnetic interference by power lines and so on, resulting in poor location accuracy. When accuracy is too low, manual spot activation mode becomes available. This mode overrides all experience playback rules, so geoXp enables it only for really low accuracy (greater than 100m), and only if user is not too far away from the intended spot playback position (in the case of slow location update time and the user has reached a new spot before an update). See Forcing spots activation for details.
IMPORTANT - forcing a spot is a plan B when something is not working properly (bad GPS or slow update time). It will interrupt all automatic experience management, until the forced content is finished. After that, all the experience logic will get back to work.
Usage
GeoXp is intended to be used as a singleton instance. It has to be created once the application starts, based on a configuration object.
Create a GeoXp instance
// import module
import GeoXp from '@mezzo-forte/geoxp';
// create configuration object
const config = { /* your configuration here */ };
// create the GeoXp instance
const geoXp = new GeoXp(config);
Configuration
GeoXp, once created, works without any external intervention. To provide this high level of automation, it has to be accurately configured according to the desired application. The configuration is provided with a json object made of three parts, each carrying the configuration information for one of the internal modules.
config: {
geo: { /* ... */ },
audio: { /* ... */ },
experience: { /* ... */ }
}
Configuration for geolocation (geo) is a simple map of positions and parameters for geofencing, configuration for audio is a list of all the audio content available.
Configuration for experience is meant to set links between positions and related content.
Each configuration section has a .options child that stores some module working parameters. If no options object is provided, GeoXp will use its hardcoded default configuration.
Geo configuration
Provides information for geolocation module configuration. See here for more details on Geo options
geo: {
positions: [
{
id: string // unique position id
label: string // position name or description
lat: number // latitude in degrees north
lon: number // longitude in degrees east
radius: number // fencing radius in meters
deadband: number // fencing deadband in meters
fetch: number // prefetching distance as ratio of the radius, from 1 to n
}
],
options: {
enableHighAccuracy: boolean // @see https://developer.mozilla.org/en-US/docs/Web/API/PositionOptions/enableHighAccuracy
accuracy: number // minimum acceptable accuracy in meters - default value = 25m
defaultDeadband: number // default fencing deadband - default value = 10m
defaultRadius: number // default fencing radius - default value = 20m
defaultFetch: number // default prefetch distance ratio default value = 1 (integer)
}
}
Audio configuration
Provides information for audio module configuration. See here for more details on Audio options
audio: {
sounds: [ // array of audio contents
{
id: string // unique content id
url: string // content url (local or remote)
}
],
options: {
test: string // url for test sound
silence: string // url for silence sound
visited: string // url for spot already visited sound
fadeInTime: number // fade in time [ms] - default value = 0 ms
fadeOutTime: number // fade out time [ms] - default value = 1000 ms
}
Experience configuration
Experience configuration provides relations between geolocation and content. See here for more details on Experience options
experience: {
patterns: [ // array of patterns, each with its spots list and parameters
{
id: string // pattern unique id
label: string // pattern name or description
disabled: bool // pattern is disabled
replay: bool // spots are automatically replayed
overlap: bool // content playback can overlap
spots: [ // array of pattern’s spots
{
id: string // spot unique id
position: // position id as in geo position configuration
audio: // audio id as in audio sound configuration
after: // id of the previous mandatory spot (see “key concepts”, “Spots order”)
notAfter: // id of the spot that prevents current spot playback (see “key concepts”, “Spots order”)
label: // spot name or descriptions
}
]
}
],
options: {
visitedFilter: number // time after an already visited spot is notified [ms] - default value = 5000 ms
cookies: { // enables pattern "visited spots" cookies
deleteOnCompletion: boolean // default option, deletes cookie when all pattern spots are visited
deleteOnLastSpot: boolean // deletes cookie when spot flagged with "last" is visited
expiration: number // [minutes] overrides cookies default expiration time (5 minutes)
}
}
}
NOTE - patterns are enabled by default. See core methods to know how to disable or re-enable them
Reload and disposal
// Creates a new geoXp instance
const geoXp = new GeoXp(config);
// Refreshes all geoXp state with the given configuration
geoXp.reload(config);
// Disposes geoXp object
geoXp.destroy();
Events subscription
GeoXp is meant to work automatically based on its configuration, so most of the interaction with it is based on events.
Its event dispatcher (geoXp.event
) is based on Node.js EventEmitter and is responsible for events notification to outside subscribers.
Three main methods are wrappped by the GeoXp
class:
geoXp.on(eventName, listener)
- adds thelistener
function to the end of the listeners array for the event namedeventName
geoXp.once(eventName, listener)
- adds a one-timelistener
function for the event namedeventName
geoXp.off(eventName, listener)
- removes the specifiedlistener
from the listener array for the event namedeventName
All other EventEmitter
properties and methos are accessible through the event
property of GeoXp
class.
Available events are:
Position update
geoXp.on('position', position => { /* ... */ })
Position update occurs every time geolocation API receives a new location. The new location is provided to the callback as Geolocation API standard position object.
Spot incoming
geoXp.on('incoming', spot => { /* ... */ })
User entered the prefetch distance of a spot. GeoXp will start related audio pre loading. The object provided as callback argument carries all the spot info based on configuration.
spot: {
id: string,
position: string (position id as in geo configuration)
audio: string (audio id as in audio configuration)
after: string (spot id as in experience pattern configuration)
}
Spot active
geoXp.on('active', spot => { /* ... */ })
A spot is being activated. GeoXp will play the content associated. The object provided as callback argument carries all the spot info based on configuration.
Spot visited
geoXp.on('visited', spot => { /* ... */ })
User entered a spot which he already visited before. The object provided as callback argument carries all the spot info based on configuration.
Spot outgoing
geoXp.on('outgoing', spot => { /* ... */ })
User exited a spot. GeoXp will stop playing related content. The object provided as callback argument carries all the spot info based on configuration.
Content playing
geoXp.on('play', audio => { /* ... */ })
Some audio content just started playing. Callback argument is an object with the audio information.
audio: {
id: string, // is the Howler instance audio id. Is composed as spotId-audioId
overlap: boolean,
playWhenReady: boolean,
spot: { // spot that caused playback
id: string, // spot id
label: string, // spot label
audio: string, // audio id
postion: string, // position id
after: string
}
audio: Howler.Howl // howler instance
}
Content ended
geoXp.on('stop', audio => { /* ... */ })
Some audio content just stopped (either for completion or because it has been stopped). Callback argument is an object with the audio information.
audio: {
id: string, // is the Howler instance audio id. Is composed as spotId-audioId
overlap: boolean,
playWhenReady: boolean,
spot: { // spot that caused playback
id: string, // spot id
label: string, // spot label
audio: string, // audio id
postion: string, // position id
after: string
}
audio: Howler.Howl // howler instance
}
Audio interaction
GeoXp manages all audio content on its own.
However, it’s possible to interact with it when needed (eg: showing audio current seek, playing / pausing audio, etc.).
In facts, the audio
property of the play
and stop
events is an Howl
oject (the Howler.js instance for audio management) that exposes all methods for audio interaction (pause, play, seek).
// Sets the volume for all audio contents, 0 to 1 max volume
geoXp.audio.setVolume(volume: number);
// Immediately stops all audio currently playing
geoXp.audio.stopAll();
// Immediately mutes / unmutes all audio currently playing
geoXp.audio.muteAll(mute: boolean);
External geolocation providers
Internal geoXp geolocation provider is the Web Geolocation API.
If needed, it could be overriden with an external one of choice (eg: Capacitor location for mobile integration, external GPS sensor, etc).
Position updates can be provided using the updateGeolocation(position)
method, passing position as Geolocation API standard position object.
To avoid unwanted updates from the internal provider, disable it by calling the internalGeolocation(false)
method.
Internal geolocation provider can be reenabled by calling internalGeolocation(true)
.
Spots content replay
By default, when user reenters a spot he already visisted before, the spot isn't replayed; instead, a visited
event for that spot is fired.
The spot replay can be triggered calling the replaySpot(id)
method, passing the id of the spot to replay. The spot is then marked as unvisited, and the playback starts immediately.
Multiple spots could be linked to the same position, so multiple visited
events could be fired at once. If you don't want to care about which spot is to be replayed, call the replaySpot()
method with no argument, so all the spots linked to the current position are marked as unvisited, and replayed following the rules defined in configuration (eg: spot order).
This behavior could be overridden using the pattern.replay
option. If a pattern is set so, its spots will replay immediately, and no visited
event is fired.
Forcing spots activation
If GPS accuracy is low and user isn't too far away from a spot location, the spot can be activated manually using the forceSpot(id)
method, passing the id of the spot to force.
GeoXp then enters manual mode (internal geolocation updates are stopped, all other audio content is stopped) and activates the desired spot. When the playback is finished (or stopped), geoXp returns to automatic mode and the experience goes on as usual.
If you want to know if manual mode is available, just call the canForceSpot(id)
passing the id of the deisired spot. If rules for manual mode are not fulfilled, it returns and error string explaing the reason why it can't be forced. Otherwise it will return undefined
.
Forcing contents is always allowed for spots that does not have a geographic position associated: in this case the only way to reproduce the audio file is to invoke forceSpot
method, and the rules described above are not applied.
Example:
"spots": [
{
"id": "s02",
"position": "p02",
"audio": "a02",
"label": "Spot 2!!"
},
{
"id": "s03",
"audio": "a03",
"label": "Spot 3!!"
},
]
In the above list, spot s02
can be forced only if all the geographic rules are respected. Spot s03
can always be forced since it does not have an associated position
.
Core methods
All GeoXp core methods are available in the documentation page.
Best practices
Designing configuration for a specific use
Behavior of geoXp covers a wide variety of applications. This broad approach means that, to guarantee the user experience good flow and consistency, some effort needs to be spent on adopting an optimal configuration for the desired result.
For example, geoXp is used for an audio guide of a museum tour.
The content is made of speeches, in which a guide is explaining historical facts about different museum spots.
This means that content cannot overlap
config.experience.patterns[x].overlap = false
that user must hear the content just once
config.experience.patterns[x].replay = false
Now, let’s change application. GeoXp is used to play ambient sounds at certain locations.
This means that content can overlap
config.experience.patterns[x].overlap = true
that user must hear the content every time is in the right location
config.experience.patterns[x].replay = true
Positions overlap
Unless content overlapping is desired, it’s better to avoid positions overlap when possible.
If two pattern spots are actually near each other, try setting radiuses in a way that fencing doesn’t overlap (maybe by setting a small radius and a big deadband: user has to be close to the position for the content to start, but the content will not stop if he walks away).
If overlapping isn’t avoidable, make sure to apply filtering with experience.options.visitedFilter
(usually 5000 or 10000 ms is enough).
Mobile integration
Most mobile browsers will block Howler and Geolocation API after some time with no user interaction, resulting in unpredictable geoXp behavior.
To avoid this, force a periodic unlocking calling the unlock()
method inside a user interaction (eg: click listerner, etc).
Contributing
To contribute to this project, fork the repository, work on a development branch and open a MR. Remember to update the changelog (CHANGELOG.md)!
Repo admins: to release a new version, follow these steps:
- verify and test changes
- verify changelog has been updated
- merge MR in
master
- release a new version with
npm run release --vers=X.Y.Z
- create a new release in the release section
Examples
Some configuration examples, for different kind of patterns, can be found inside the example-patterns. A basic application, with event usage, can be found inside the example-app folder.
Credits
- concept - Mezzo Forte
- development - Francesco Cretti & Giuliano Buratti
- music for example application - Bensound