Omakase Player
Prerequisites
Omakase Player can be loaded as UMD module inside HTML page. If loaded as UMD module it requires hls.js loaded before Omakase Player:
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@byomakase/omakase-player@0.9.2-SNAPSHOT.1724678052/dist/omakase-player.umd.min.js"></script>
Omakase Player can be used as ES module and CJS module as well.
If used with modern Typescript / Javascript frameworks (such as Angular, React or Vue), it is recommended to simply install Omakase Player as dependency into package.json
:
npm install @byomakase/omakase-player
Optionally, you can include default Omakase Player CSS stylesheet or import and use omakase-player.scss
SCSS stylesheet.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@byomakase/omakase-player@0.9.2-SNAPSHOT.1724678052/dist/style.min.css">
Stylesheet references default player overlay icons, help menu icons and default styles for video safe zones. All of which can be overridden.
Player Initialization
Omakase Player requires div as a placeholder for HTML5 player.
<div id="omakase-player"></div>
Initialize the player by providing div id in player configuration. If used as UMD module, Omakase Player objects are available in global omakase
namespace:
let omakasePlayer = new omakase.OmakasePlayer({
playerHTMLElementId: 'omakase-player',
});
Once player is initialized we can load hls video stream by providing stream URL and stream frame rate:
omakasePlayer.loadVideo('https://my-server.com/myvideo.m3u8', 25).subscribe({
next: (video) => {
console.log(`Video loaded. Duration: ${video.duration}, totalFrames: ${video.totalFrames}`)
}
})
Media chrome controls visibility can be toggled with the mediaChrome
property. Possible values are enabled
(always enabled), disabled
(always disabled) and fullscreen-only
(enabled in fullscreen, disabled otherwise). Default value is fullscreen-only
in regular mode and enabled
in detached mode.
let omakasePlayer = new omakase.OmakasePlayer({
mediaChrome: 'enabled'
});
Video API
Complete list of Video API methods is available in API Reference Docs
Video playback control
Video playback control is achieved through Video API.
omakasePlayer.video.play()
omakasePlayer.video.play().subscribe(() => {
console.log(`Play started`);
})
omakasePlayer.video.pause()
omakasePlayer.video.seekToTime(123.45).subscribe({
next: (result) => {
if (result) {
console.log(`Seek to timestamp success`);
}
}
})
omakasePlayer.video.seekToFrame(123).subscribe({
next: (result) => {
if (result) {
console.log(`Seek to frame success`);
}
}
})
omakasePlayer.video.toggleMuteUnmute();
Events
Before or after loading video stream, we can subscribe to various events. All events are available in API objects as Observables or we can subscribe to events by using EventEmmiter like methods.
Example how to subscribe to video loaded event Observable:
omakasePlayer.video.onVideoLoaded$.subscribe({
next: (event) => {
if (event) {
let video = event.video;
console.log(`Video loaded. Duration: ${video.duration}, totalFrames: ${video.totalFrames}`)
}
}
})
Example how to subscribe to video time change event using EventEmmiter like methods:
omakasePlayer.on(omakasePlayer.EVENTS.OMAKASE_VIDEO_TIME_CHANGE, (event) => {
console.log(`Video time change. Timestamp: ${event.currentTime} => ${omakasePlayer.video.formatToTimecode(event.currentTime)}. Frame: ${event.frame}`)
})
Video playback events subscription examples:
omakasePlayer.video.onPlay$.subscribe({
next: (event) => {
console.log(`Video play. Timestamp: ${event.currentTime} => ${omakasePlayer.video.formatToTimecode(event.currentTime)}`)
}
})
omakasePlayer.video.onPause$.subscribe({
next: (event) => {
console.log(`Video pause. Timestamp: ${event.currentTime} => ${omakasePlayer.video.formatToTimecode(event.currentTime)}`)
}
})
omakasePlayer.video.onSeeked$.subscribe({
next: (event) => {
console.log(`Video seeked. Timestamp: ${event.currentTime} => ${omakasePlayer.video.formatToTimecode(event.currentTime)}`)
}
})
omakasePlayer.video.onVideoTimeChange$.subscribe({
next: (event) => {
console.log(`Video time change. Timestamp: ${event.currentTime} => ${omakasePlayer.video.formatToTimecode(event.currentTime)}. Frame: ${event.frame}`)
}
})
Detached video player
To enable full video detaching in Omakase Player we need to instantiate another detached instance of Omakase Player on same host, and tell our local instance where to find it.
Local player instance configuration on https://my-server.com/omp-player
:
let omakasePlayer = new omakase.OmakasePlayer({
playerHTMLElementId: 'omakase-player',
detachedPlayerUrl: 'https://my-server.com/player/omp-player-detached'
});
Detached player instance configuration on https://my-server.com/omp-player-detached
:
let omakasePlayer = new omakase.OmakasePlayer({
playerHTMLElementId: 'omakase-player',
detachedPlayer: true
});
We can now load a video, detach it to independent browser window and play it!:
omakasePlayer.loadVideo('https://my-server.com/myvideo.m3u8', 25).subscribe({
next: (video) => {
console.log(`Video loaded`)
omakasePlayer.video.detachVideoWindow().subscribe(() => {
console.log(`Video detached`)
omakasePlayer.video.play();
})
}
})
Due to security and usability policies, most modern browsers require a user interaction before allowing certain actions, such as video autoplay or fullscreen initiation.
It could be that on-time-only user interaction (such as clicking on play button in detached player) is needed before video playback or switching to fullscreen playback after video detaching.
Hls.js
We can fetch hls.js instance through API, as well as subscribe to hls.js events:
let hlsInstance = omakasePlayer.video.getHls();
hlsInstance.on('hlsManifestParsed', (event, data) => {
console.log(`HLS manifest parsed`, data);
})
Utilities
omakasePlayer.video.addSafeZone({
topRightBottomLeftPercent: [10, 10, 10, 10]
})
omakasePlayer.video.addSafeZone({
aspectRatio: "16/9"
})
omakasePlayer.video.toggleFullscreen();
Audio API
Complete list of Audio API methods is available in API Reference Docs.
Few common usages of Audio API:
let audioTracks = omakasePlayer.audio.getAudioTracks();
let activeAudioTrack = omakasePlayer.audio.getActiveAudioTrack();
omakasePlayer.audio.onAudioSwitched$.subscribe({
next: (event) => {
console.log(`Audio switched`, event)
}
})
omakasePlayer.audio.setActiveAudioTrack(audioTracks[1].id);
AudioContext and audio routing
omakasePlayer.audio.createAudioContext(2, 4);
omakasePlayer.audio.routeAudioInputOutputNode({
inputNumber: 0,
outputNumber: 1,
connected: true
})
omakasePlayer.audio.routeAudioInputOutputNode({
inputNumber: 1,
outputNumber: 1,
connected: false
})
Timeline
Timeline is initialized by defining div placeholder and calling createTimeline()
API method with optional configuration and style settings.
<div id="omakase-timeline"></div>
omakasePlayer.createTimeline({
timelineHTMLElementId: 'omakase-timeline',
thumbnailVttUrl: 'https://my-server.com/thumbnails/timeline.vtt',
style: {
stageMinHeight: 300,
backgroundFill: '#fef9f7'
}
}).subscribe({
next: (timelineApi) => {
console.log(`Timeline created`)
}
})
Timeline Lanes
Omakase Player supports adding various Timeline Lanes:
- Scrubber Lane
- Thumbnail Lane
- Marker Lane
- Subtitles Lane
- Audio Track Lane
- Label Lane
- Scrollbar Lane
- Line Chart Lane
- Bar Chart Lane
- Og Chart Lane
Timeline Lanes are added after Timeline creation. Base Timeline Lanes can be configured, styled and extended with custom functionalities.
Scrubber Lane
Scrubber Lane is created automatically. Scrubber Lane instance can be fetched by using Timeline API after Timeline is created
omakasePlayer.createTimeline().subscribe({
next: (timelineApi) => {
console.log(`Timeline created`);
let scrubberLane = omakasePlayer.timeline.getScrubberLane();
scrubberLane.style = {
backgroundFill: '#dfe0e2',
tickFill: '#08327d',
timecodeFill: '#08327d'
}
}
})
Thumbnail Lane
Thumbnail Lane loads thumbnails from VTT file and shows them on timeline. In example below thumbnail mouse click event is handled.
let thumbnailLane = new omakase.ThumbnailLane({
description: 'Thumbnails',
vttUrl: 'https://my-server.com/thumbnails.vtt'
})
omakasePlayer.timeline.addTimelineLane(thumbnailLane);
thumbnailLane.onClick$.subscribe({
next: (event) => {
if (event.thumbnail.cue) {
console.log(`Seeking to to thumbnail: ${omakasePlayer.video.formatToTimecode(event.thumbnail.cue.startTime)}`);
omakasePlayer.video.seekToTime(event.thumbnail.cue.startTime).subscribe({
next: () => {
console.log(`Seek complete`);
}
})
}
}
})
Marker Lane
Marker Lane can be populated from VTT file or by using API methods directly:
let markerLane = new omakase.MarkerLane({
description: 'Markers',
vttUrl: 'https://demo.player.byomakase.org/data/thumbnails/timeline.vtt',
markerCreateFn: (cue, index) => {
return new omakase.PeriodMarker({
timeObservation: {
start: cue.startTime,
end: cue.endTime
},
text: `${cue.text}`,
editable: true,
style: {
renderType: 'lane',
color: index % 2 ? '#2677bb' : '#dd6464',
symbolType: 'triangle'
}
})
},
markerProcessFn: (marker, index) => {
marker.onClick$.subscribe({
next: (event) => {
console.log(`Clicked on marker with text: `, marker.text)
}
})
marker.onChange$.subscribe({
next: (event) => {
console.log(`Marker time observation change: `, event)
}
})
}
})
omakasePlayer.timeline.addTimelineLane(markerLane);
markerLane.addMarker(new omakase.MomentMarker({
timeObservation: {
time: 100
},
style: {
renderType: 'spanning',
color: '#ff0000',
symbolType: 'circle'
}
}));
Subtitles Lane
Subtitles Lane is used for subtitles visualisation on timeline. It is populated from VTT file.
let subtitlesLane = new omakase.SubtitlesLane({
description: 'Subtitles',
vttUrl: 'https://my-server.com/subtitles.vtt'
})
omakasePlayer.timeline.addTimelineLane(subtitlesLane);
Audio Track Lane
Audio Track Lane is used for audio track visualisation.
let audioTrackLane = new omakase.AudioTrackLane({
description: 'Audio Track',
vttUrl: 'https://my-server.com/audio-track.vtt'
})
omakasePlayer.timeline.addTimelineLane(audioTrackLane);
Label Lane
Label Lane is usually used on timeline as grouping lane that contains other timeline components, such as timeline buttons and labels.
let labelLane = new omakase.LabelLane({
description: 'Label lane',
text: 'Right pane label',
style: {
backgroundFill: '#a5a6a9',
textFill: '#f45844',
textFontSize: 20
}
});
omakasePlayer.timeline.addTimelineLane(labelLane);
Scrollbar Lane
Scrollbar Lane contains Timeline scrollbar that controls timeline zoom and scroll.
let scrollbarLane = new omakase.ScrollbarLane({
description: ''
});
omakasePlayer.timeline.addTimelineLane(scrollbarLane);
Line Chart Lane
Line Chart Lane for data visualisation.
let lineChartLane = new omakase.LineChartLane({
vttUrl: 'https://my-server.com/line-chart.vtt',
yMax: 100,
yMin: -50,
style: {
pointWidth: 5,
lineStrokeWidth: 2
},
});
omakasePlayer.timeline.addTimelineLane(lineChartLane);
Bar Chart Lane
Bar Chart Lane for data visualisation.
let barChartLane = new omakase.BarChartLane({
vttUrl: 'https://my-server.com/bar-chart.vtt',
description: 'Bar Chart',
valueMax: 120,
valueMin: 50,
valueTransformFn: (value) => {
return value;
},
itemProcessFn: (item, index) => {
item.onClick$.subscribe({
next: (event) => {
console.log(event, item)
}
})
},
valueInterpolationStrategy: 'max'
});
omakasePlayer.timeline.addTimelineLane(barChartLane);
OG Chart Lane
OG Chart Lane for data visualisation.
let ogChartLane = new omakase.OgChartLane({
vttUrl: 'https://my-server.com/og-chart.vtt',
description: 'Bar Chart',
valueMax: 120,
valueMin: 50,
valueTransformFn: (value) => {
return value;
},
itemProcessFn: (item, index) => {
item.onClick$.subscribe({
next: (event) => {
console.log(event, item)
}
})
},
valueInterpolationStrategy: 'max'
});
omakasePlayer.timeline.addTimelineLane(ogChartLane);
Timeline Lane API
Timeline Lane Nodes
Timeline Lane Nodes can be added to Timeline Lane instances with addTimelineNode()
API method. Nodes types that can be added are:
In this example, Timeline zoom in and zoom out buttons are added to Scrubber Lane:
let scrubberLane = omakasePlayer.timeline.getScrubberLane();
let zoomInButton = new omakase.ImageButton({
src: `https://my-server.com/images/plus-circle.svg`,
width: 30,
height: 30,
listening: true
})
zoomInButton.onClick$.subscribe({
next: (event) => {
omakasePlayer.timeline.zoomInEased().subscribe();
}
})
let zoomOutButton = new omakase.ImageButton({
src: `https://my-server.com/images/minus-circle.svg`,
width: 30,
height: 30,
listening: true
})
zoomOutButton.onClick$.subscribe({
next: (event) => {
omakasePlayer.timeline.zoomOutEased().subscribe();
}
});
[zoomOutButton, zoomInButton].forEach(button => {
scrubberLane.addTimelineNode({
width: button.config.width,
height: button.config.height,
justify: 'end',
timelineNode: button,
})
});
Minimize, Maximize
Timeline Lane in Timeline can be minimized or maximized by calling methods from TimelineLaneApi
.
In this example, Grouping Label Lane is created at specific index on Timeline. Minimize and Maximize Text Label action buttons are created and added to Timeline Lane left pane.
let markerLaneGroup = new omakase.LabelLane({
text: 'Marker Lane Group',
style: {
backgroundFill: '#c2b4a6',
textFill: '#fbfbfb'
}
});
omakasePlayer.timeline.addTimelineLaneAtIndex(markerLaneGroup, omakasePlayer.timeline.getTimelineLanes().findIndex(p => p.id === markerLane.id));
let textLabelMinimize = new omakase.TextLabel({
text: `Minimize`,
listening: true,
style: {
align: 'center',
verticalAlign: 'middle',
fill: '#ffffff',
backgroundFill: '#f45844',
backgroundBorderRadius: 3
}
});
let textLabelMaximize = new omakase.TextLabel({
text: `Maximize`,
listening: true,
style: {
align: 'center',
verticalAlign: 'middle',
fill: '#ffffff',
backgroundFill: '#46454b',
backgroundBorderRadius: 3
}
});
textLabelMinimize.onClick$.subscribe({
next: () => {
if (!markerLane.isMinimized()) {
markerLane.minimizeEased().subscribe()
}
}
})
textLabelMaximize.onClick$.subscribe({
next: () => {
if (markerLane.isMinimized()) {
markerLane.maximizeEased().subscribe()
}
}
});
[textLabelMinimize, textLabelMaximize].forEach(textLabel => {
markerLaneGroup.addTimelineNode({
width: 60,
height: 22,
justify: 'start',
margin: [0, 5, 0, 0],
timelineNode: textLabel
});
})
Marker List API
Marker list is initialized by defining div placeholder and calling createMarkerList()
API method with optional configuration.
The marker list web component will be added into a html element with id defined in markerListHTMLElementId
. If this parameter is not provided, it will default toomakase-player-marker-list
. Following code will instantiate an empty marker list.
<div id="marker-list"></div>
omakasePlayer.createMarkerList({
markerListHTMLElementId: 'marker-list'
})
Loading markers from a VTT file
A marker list can be loaded from a VTT file. In this case a function to create a marker list item from VTT file cues can also be provided, as well as HTML content to render while the file is loading.
<div id="marker-list"></div>
<template id="loading-template">
</template>
const colors = ['red', 'green', 'blue']
omakasePlayer.createMarkerList({
loadingHTMLElementId: 'loading-template',
vttUrl: './markers.vtt',
vttMarkerCreateFn: (cue, index) => ({
name: 'VTT Marker ' + index,
timeObservation: {
start: cue.startTime,
end: cue.endTime
},
style: {
color: colors[index % colors.length]
},
data: {
custom_key: 'custom value'
}
})
})
Deep linking with timeline lanes
Marker list can also be linked to one or multiple timeline lanes. If linked in this way, the markers from the timeline lane(s) will appear on the marker list and they will stay in sync regardless if markers are added to Marker List or timeline lane(s).
omakasePlayer.createMarkerList({
source: [
omakasePlayer.timeline.getTimelineLane('marker-lane-1'),
omakasePlayer.timeline.getTimelineLane('marker-lane-2')
]
})
Thumbnails
Thumbnail VTT file can be passed using thumbnailVttFile
property. If provided, it will be used to automatically set the thumbnail to the closest vtt cue based on the marker start time.
You can also use thumbnailFn
property for defining a function that will provide a thumbnail url for any given time.
omakasePlayer.createMarkerList({
thumbnailVttFile: omakasePlayer.timeline.thumbnailVttFile
})
CRUD methods
The following methods are available on the marker list. Usage examples are shown below.
addMarker
updateMarker
removeMarker
toggleMarker
omakasePlayer.createMarkerList().subscribe(markerList => {
const marker = markerList.addMarker({
name: 'Marker',
timeObservation: {
start: 100,
end: 200
},
style: {
color: 'red'
}
})
markerList.updateMarker(marker.id, {timeObservation: {start: 100, end: 300}})
markerList.toggleMarker(marker.id)
markerList.removeMarker(marker.id)
})
Styling and templating
Marker list HTML and style can be customised by passing a css file url and template element ids.
A template for the marker list row can include slots to render data or trigger actions. The following slots are predefined:
color
(from marker style
property)thumbnail
(found in the thumbnailVttFile
, if provided)name
(marker text
property)track
(name of the linked timeline lane, if applicable)start
(start
or time
property from marker timeObservation
)end
(end
property from marker timeObservation
)duration
(difference between end
and start
)remove
(triggers marker removal)
Beside predefined slots, dynamic slots can be used to display custom data or trigger custom actions. Custom data slots must be prefixed with data-
and custom actions slots must be prefixed with action-
.
The parameter styleUrl
can be an array to provide multiple css files.
<template id="row-template">
<div slot="color"></div>
<div slot="name"></div>
<div slot="start"></div>
<div slot="end"></div>
<div class="actions">
<span slot="action-edit"></span>
<span slot="remove"></span>
</div>
</template>
<template id="header-template">
</template>
<template id="empty-template">
</template>
omakasePlayer.createMarkerList({
templateHTMLElementId: 'row-template',
headerHTMLElementId: 'header-template',
emptyHTMLElementId: 'empty-template',
styleUrl: './style.css'
})
Events
The following action events are provided:
onMarkerClick$
(triggered when the marker row is clicked)onMarkerAction$
(triggered when a custom element provided with an action-<name>
slot is clicked)
The following marker lifecycle events are provided:
onMarkerCreate$
onMarkerUpdate$
onMarkerDelete$
onMarkerInit$
Some usage examples are shown below:
omakasePlayer.createMarkerList().subscribe(markerList => {
markerList.onMarkerClick$.subscribe(event => {
console.log(event.marker)
})
markerList.onMarkerAction$.subscribe(event => {
console.log(event.action, event.marker)
})
})
Destroy
Marker list can be destroyed with the destroy()
method. This cleans up the marker list resources and removes it from the DOM.
Subtitles API
Complete list of Audio API methods is available in API Reference Docs.
Omakase Player automatically identifies all available subtitles VTT tracks from stream manifest and makes them available through Subtitles API.
omakasePlayer.subtitles.onSubtitlesLoaded$.subscribe({
next: (event) => {
let subtitlesVttTracks = omakasePlayer.subtitles.getTracks();
omakasePlayer.subtitles.showTrack(subtitlesVttTracks[0].id)
}
})
Subtitles can be imported from external VTT file:
omakasePlayer.subtitles.createVttTrack({
id: '0',
src: 'https://my-server.com/subtitles.vtt',
label: 'English (US)',
language: 'en-us',
default: true
}).subscribe({
next: (subtitlesVttTrack) => {
console.log(`Subtitles successfully created`)
}
})
Development
Player build & build watch
npm install ci
npm run dev
Production build
npm install ci
npm run prod
Production artefacts that need to be published to NPM are created in /dist
folder
Known limitations
- Firefox browser is not supported as it doesn't support
requestVideoFrameCallback
function