This document describes how the Player Expert application integrates Amalia (@ina/amalia) in production.
It is intended as a practical reference for:
- copying the Amalia bundle into a host application
- loading Amalia at runtime
- creating the player and plugins as Web Components
- configuring metadata sources (
dataSources) and plugin routing - understanding event names used by Player Expert
This guide is based on real code from Player Expert:
src/app/player-expert/service/amalia-player-service.tssrc/app/player-expert/asset-details/asset-details.component.ts
Player Expert expects Amalia to be available as a single bundle served by the host app:
assets/amalia-<version>.min.js
Where <version> matches the installed dependency version (@ina/amalia) in the host.
In Amalia core, the bundle is produced under:
dist/amalia/amalia-<version>.min.js
The bundle is generated by concatenating Angular build artifacts (runtime.js, polyfills.js, scripts.js, main.js).
Player Expert loads the script dynamically and only once.
Behavior:
- compute the Amalia version from
package.jsondependencies - check if a script already exists:
script[src="assets/amalia-<version>.min.js"]
- if missing:
- create
<script type="module" src="assets/amalia-<version>.min.js"> - append it to
document.body
- create
This avoids double-registering custom elements.
Player Expert creates the player element programmatically.
Attributes set by Player Expert:
player-id: logical identifier used to connect plugins to the right player instanceid:ajs-<playerId>type:videooraudioconfig: JSON string (stringified configuration)class: uses'timebar'
Player Expert always appends a control bar to the player:
<amalia-control-bar player-id="<playerId>">
The configuration is built by Player Expert in AmaliaPlayerService.configurePlayer(...).
Key points:
-
uses a different base config for video vs audio
-
VideoPlayerConfigVideoPlayerConfig (example)
export class VideoPlayerConfig { tcOffset = 0; player = { backwardsSrc: '', src: '', autoplay: false, hls: { enable: true, config: { maxBufferLength: 12, maxMaxBufferLength: 60, backBufferLength: 0, enableWorker: false } }, crossOrigin: 'anonymous', media: 'VIDEO', }; thumbnail = { baseUrl: '', enableThumbnail: true, tcParam: 'start' }; dataSources = []; debug = true; logLevel = "debug"; pluginsConfiguration = { 'CONTROL_BAR-PLAYER': { data: [ { label: 'Barre de progression', control: 'progressBar', priority: 1 }, { label: "Télécharger", control: "download", icon: "download", zone: 1, order: 1, priority: 5, key: 'd', }, /*{ "label": "Sous titres", "control": "subtitles", "icon": "subtitle", "zone": 3, "priority": 3 },*/ { label: 'Playback rate custom steps', control: 'playbackRateCustomSteps' }, { label: 'Playback rate steps', control: 'playbackRateSteps' }, { label: 'Capture', control: 'download', icon: 'screenshot', key: 'c', zone: 1, order: 2, data: { 'tcParam': 'start', 'href': '' }, priority: 2 }, { label: 'Playback Rate', control: 'playbackRate', zone: 1, priority: 3, order: 3 }, { label: 'Aller au début du média', icon: 'backward-start', control: 'backward-start', zone: 2, priority: 5, key: 'Home', notInMenu: true }, { label: 'Retour rapide', icon: 'backward', control: 'backward', zone: 2, priority: 3, key: 'Shift + ArrowLeft' }, { label: 'Retour ralenti', icon: 'slow-backward', control: 'slow-backward', zone: 2, priority: 4, key: 'Alt + ArrowLeft', notInMenu: true }, { label: 'Retour 5 secondes par 5 secondes', icon: 'backward-5seconds', control: 'backward-5seconds', zone: 2, priority: 2, key: 'Control + ArrowLeft' }, { label: 'Retour image par image', icon: 'backward-frame', control: 'backward-frame', zone: 2, priority: 3, key: 'ArrowLeft' }, { label: 'Pause / Lire', control: 'playPause', zone: 2, priority: 2, key: 'espace' }, { label: 'Avance image par image', icon: 'forward-frame', control: 'forward-frame', zone: 2, priority: 3, key: 'ArrowRight' }, { label: 'Avance 5 secondes par 5 secondes', icon: 'forward-5seconds', control: 'forward-5seconds', zone: 2, priority: 2, key: 'Control + ArrowRight' }, { label: 'Avance ralentie', icon: 'slow-forward', control: 'slow-forward', zone: 2, priority: 4, key: 'Alt + ArrowRight', notInMenu: true }, { label: 'Avance rapide', icon: 'forward', control: 'forward', zone: 2, priority: 3, key: 'Shift + ArrowRight' }, { label: 'Aller à la fin du média', icon: 'forward-end', control: 'forward-end', zone: 2, priority: 5, key: 'End', notInMenu: true }, { label: 'Désactiver le son', control: 'volume', zone: 3, priority: 2, key: 'm', data: { 'channelMergeVolume': false, 'channelMergerNode': '' }, }, { label: 'Plein écran', control: 'toggleFullScreen', icon: 'fullscreen', zone: 3, priority: 2, key: 'f' }, { label: 'Aspect ratio', control: 'aspectRatio', zone: 3, priority: 5, key: 'a' }, { label: 'Figer', control: 'pinControls', icon: 'pin', zone: 3, priority: 4, key: 'p', }, { label: 'Afficher les vitesses', control: 'displaySlider', icon: 'slider', zone: 3, priority: 5, key: 'v', }, { label: 'Plus d\'options', control: 'menu', icon: 'dots', zone: 3, priority: 3, key: 'r' } ], pinnedControls: true, } }; }
-
AudioPlayerConfigAudioPlayerConfig (example)
export class AudioPlayerConfig { tcOffset = 0; player = { backwardsSrc: '', src: '', autoplay: false, hls: { enable: true, config: { maxBufferLength: 12, maxMaxBufferLength: 60, backBufferLength: 0, enableWorker: false } }, crossOrigin: 'anonymous', media: 'AUDIO', }; thumbnail = { baseUrl: '', enableThumbnail: false, tcParam: 'start' }; dataSources = []; debug = true; logLevel = "debug"; pluginsConfiguration = { 'CONTROL_BAR-PLAYER': { data: [ { label: 'Barre de progression', control: 'progressBar', priority: 1 }, { label: "Télécharger", control: "download", icon: "download", zone: 1, order: 2, data: { href: "" }, priority: 5, key: 'd', }, /*{ "label": "Sous titres", "control": "subtitles", "icon": "subtitle", "zone": 3, "priority": 3 },*/ { label: 'Playback rate custom steps', control: 'playbackRateCustomSteps' }, { label: 'Playback rate steps', control: 'playbackRateSteps' }, { label: 'Playback Rate', control: 'playbackRate', zone: 1, priority: 3, order: 3 }, { label: 'Aller au début du média', icon: 'backward-start', control: 'backward-start', zone: 2, priority: 5, key: 'Home', notInMenu: true }, { label: 'Retour rapide', icon: 'backward', control: 'backward', zone: 2, priority: 3, key: 'Shift + ArrowLeft' }, { label: 'Retour ralenti', icon: 'slow-backward', control: 'slow-backward', zone: 2, priority: 4, key: 'Alt + ArrowLeft', notInMenu: true }, { label: 'Retour 1 seconde par 1 seconde', icon: 'backward-second', control: 'backward-second', zone: 2, priority: 2, key: 'ArrowLeft' }, { label: 'Pause / Lire', control: 'playPause', zone: 2, priority: 2, key: 'espace' }, { label: 'Avance 1 seconde par 1 seconde', icon: 'forward-second', control: 'forward-second', zone: 2, priority: 2, key: 'ArrowRight' }, { label: 'Avance ralentie', icon: 'slow-forward', control: 'slow-forward', zone: 2, priority: 4, key: 'Alt + ArrowRight', notInMenu: true }, { label: 'Avance rapide', icon: 'forward', control: 'forward', zone: 2, priority: 3, key: 'Shift + ArrowRight' }, { label: 'Aller à la fin du média', icon: 'forward-end', control: 'forward-end', zone: 2, priority: 5, key: 'End', notInMenu: true }, { label: 'Désactiver le son', control: 'volume', zone: 3, priority: 2, key: 'm', data: { 'channelMergeVolume': false, 'channelMergerNode': '' }, }, { label: 'Plein écran', control: 'toggleFullScreen', icon: 'fullscreen', zone: 3, priority: 2, key: 'f' }, { label: 'Figer', control: 'pinControls', icon: 'pin', zone: 3, priority: 4, key: 'p', }, { label: 'Afficher les vitesses', control: 'displaySlider', icon: 'slider', zone: 3, priority: 5, key: 'v', }, { label: 'Plus d\'options', control: 'menu', icon: 'dots', zone: 3, priority: 3, key: 'r' } ], pinnedControls: true, } }; }
-
-
sets core fields:
player.srcplayer.hls.config.startPositiontcOffsetthumbnail.baseUrl(e.g....?width=320)loadMetadataOnDemand = truedataSources = []initially, populated later
Control bar configuration is also customized by removing controls depending on media type.
Player Expert creates plugin containers via AmaliaPlayerService.
Element: amalia-timeline
Player Expert config shape:
metadataIdsdata.resourceType(stock|flux)data.tcIn/data.durationfor slicingdata.tvDaysEnabled(optional)
Element: amalia-transcription
Player Expert config includes:
metadataIdsdata.resourceType(stock|flux)data.tcIn/data.durationfor slicing- UI flags (progress bar, autoscroll, etc.)
Element: amalia-subtitles
Similar to transcription, but for subtitle metadata.
Element: amalia-storyboard
Player Expert passes:
data.baseUrl(imagettes endpoint)data.tcParam(e.g.start)- theme + layout (items per line)
Element: amalia-histogram
Used for audio analysis/visualization, with a dedicated plugin configuration.
Element: amalia-time-bar
Player Expert sets:
data.timeFormat:ffor stockhoursfor flux
data.theme = 'outside'- optional
first_tcfor stock
Element: amalia-annotation
Created with a richer config (categories/keywords, UI behavior).
Player Expert resolves autocomplete data through its backend before creating the plugin.
Player Expert builds a list of metadata endpoints (listOfMetadata) and then injects them into Amalia as dataSources.
Player Expert frequently includes:
format=AMALIA(orformat=amalia-motfor word-based formats)plugin=<PLUGIN_NAME>(e.g.TIMELINE,ANNOTATION)clientId=<id>(namespacing per plugin instance)
Concrete examples from Player Expert:
/segments/flux?channel=<channel>&startDate=<from>&endDate=<to>&format=AMALIA&plugin=TIMELINE&clientId=timelines
/segments/stock?itemBusinessIdentifier=<id>&tcin=<tcIn>&tcout=<tcOut>&format=AMALIA&plugin=TIMELINE&clientId=timelines
/px/item/instances-segments?item=<businessId>&tcin=<...>&tcout=<...>&clientId=DOCUMENTS_LIES-
/search/plugin?pluginName=subtitles&format=amalia-mot&clientId=subtitles<params>
/search/plugin?pluginName=transcriptions&format=amalia-mot&clientId=transcription-<algo><params>&algoname=<algo>
/segments/flux?...&format=AMALIA&plugin=ANNOTATION&clientId=annotations/segments/stock?...&format=AMALIA&plugin=ANNOTATION&clientId=annotations
Player Expert sets Authorization headers on datasources:
Authorization: Bearer <token>
Some endpoints are fetched with:
Accept: application/x-msgpack
Amalia’s MetadataManager has support for refreshing Authorization headers.
Player Expert waits for multiple conditions before creating the full Amalia UI.
It uses polling helpers such as Utils.waitFor(...) with:
- interval:
10ms - timeout:
30000ms
Example conditions in AssetDetailsComponent:
documentsLieCompletepluginsInfoAreComplete()(documents liés + plugins from LDD + “extraits utilisateurs”)
Once complete, Player Expert:
- reinitializes containers
- (optionally) reopens last grid / plugins disposition
Player Expert defines and listens/emits these namespaced event constants:
-
Player lifecycle:
ina.player.PLAYINGina.player.METADATA_LOADEDina.player.CONTROL_BAR_TOGGLED
-
Annotation workflow:
ina.annotation.ADDina.annotation.PATCHina.annotation.REMOVEina.annotation.EDITINGina.annotation.EDITING.CANCELEDina.OPEN_NOTILUS_MATERIAL
-
Contribution juridique integration:
ina.CONTRIBUTION_JURIDIQUE_ASK_FOR_CURRENT_TIMEina.CONTRIBUTION_JURIDIQUE_GET_CURRENT_TIMEina.CONTRIBUTION_JURIDIQUE_SET_CURRENT_TIMEina.CONTRIBUTION_JURIDIQUE_ASK_FOR_DURATIONina.CONTRIBUTION_JURIDIQUE_GET_DURATION
-
Timelines:
ina.TIMELINE_EXPORT_TV_DAYS
If the host app template compilation complains about unknown elements:
- Angular apps must enable
CUSTOM_ELEMENTS_SCHEMA.
If the host injects the Amalia script multiple times, custom elements can fail to register.
Player Expert avoids this by checking for the existing <script> before appending.
Check:
Authorization: Bearer <token>header is present- token refresh strategy (Amalia
MetadataManagerrefresh logic) - correct
Acceptheader for msgpack endpoints
src/app/player-expert/service/amalia-player-service.tssrc/app/player-expert/asset-details/asset-details.component.ts