mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-31 00:11:59 +02:00
Add video, audio and subtitle track selection
This commit is contained in:
67
clapper_src/controls.js
vendored
67
clapper_src/controls.js
vendored
@@ -5,6 +5,9 @@ var Controls = GObject.registerClass({
|
|||||||
'position-seeking-changed': {
|
'position-seeking-changed': {
|
||||||
param_types: [GObject.TYPE_BOOLEAN]
|
param_types: [GObject.TYPE_BOOLEAN]
|
||||||
},
|
},
|
||||||
|
'track-change-requested': {
|
||||||
|
param_types: [GObject.TYPE_STRING, GObject.TYPE_INT]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}, class ClapperControls extends Gtk.HBox
|
}, class ClapperControls extends Gtk.HBox
|
||||||
{
|
{
|
||||||
@@ -44,6 +47,16 @@ var Controls = GObject.registerClass({
|
|||||||
this.positionAdjustment = this.positionScale.get_adjustment();
|
this.positionAdjustment = this.positionScale.get_adjustment();
|
||||||
this.pack_start(this.positionScale, true, true, 0);
|
this.pack_start(this.positionScale, true, true, 0);
|
||||||
|
|
||||||
|
this.videoTracksButton = this.addPopoverButton(
|
||||||
|
'emblem-videos-symbolic'
|
||||||
|
);
|
||||||
|
this.audioTracksButton = this.addPopoverButton(
|
||||||
|
'emblem-music-symbolic'
|
||||||
|
);
|
||||||
|
this.subtitleTracksButton = this.addPopoverButton(
|
||||||
|
'media-view-subtitles-symbolic'
|
||||||
|
);
|
||||||
|
|
||||||
this.volumeButton = new Gtk.ScaleButton({
|
this.volumeButton = new Gtk.ScaleButton({
|
||||||
icons: [
|
icons: [
|
||||||
'audio-volume-muted-symbolic',
|
'audio-volume-muted-symbolic',
|
||||||
@@ -98,6 +111,48 @@ var Controls = GObject.registerClass({
|
|||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addPopoverButton(iconName, size)
|
||||||
|
{
|
||||||
|
let button = this.addButton(iconName, size);
|
||||||
|
|
||||||
|
button.popover = new Gtk.Popover({
|
||||||
|
relative_to: button
|
||||||
|
});
|
||||||
|
button.popoverBox = new Gtk.VBox({
|
||||||
|
margin_top: 4,
|
||||||
|
margin_bottom: 4,
|
||||||
|
});
|
||||||
|
button.popover.add(button.popoverBox);
|
||||||
|
button.connect('clicked', () => button.popover.popup());
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
addRadioButtons(box, array, activeId)
|
||||||
|
{
|
||||||
|
let group = null;
|
||||||
|
|
||||||
|
for(let el of array) {
|
||||||
|
let radioButton = new Gtk.RadioButton({
|
||||||
|
label: el.label,
|
||||||
|
group: group,
|
||||||
|
});
|
||||||
|
radioButton.trackType = el.type;
|
||||||
|
radioButton.trackId = el.value;
|
||||||
|
|
||||||
|
if(radioButton.trackId === activeId)
|
||||||
|
radioButton.set_active(true);
|
||||||
|
if(!group)
|
||||||
|
group = radioButton;
|
||||||
|
|
||||||
|
radioButton.connect(
|
||||||
|
'toggled', this._onTrackRadioButtonToggled.bind(this, radioButton)
|
||||||
|
);
|
||||||
|
box.add(radioButton);
|
||||||
|
}
|
||||||
|
box.show_all();
|
||||||
|
}
|
||||||
|
|
||||||
setDefaultWidgetBehaviour(widget)
|
setDefaultWidgetBehaviour(widget)
|
||||||
{
|
{
|
||||||
widget.can_focus = false;
|
widget.can_focus = false;
|
||||||
@@ -139,6 +194,18 @@ var Controls = GObject.registerClass({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onTrackRadioButtonToggled(self, radioButton)
|
||||||
|
{
|
||||||
|
if(!radioButton.get_active())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.emit(
|
||||||
|
'track-change-requested',
|
||||||
|
radioButton.trackType,
|
||||||
|
radioButton.trackId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
_onPositionScaleButtonPressEvent()
|
_onPositionScaleButtonPressEvent()
|
||||||
{
|
{
|
||||||
this.isPositionSeeking = true;
|
this.isPositionSeeking = true;
|
||||||
|
@@ -21,6 +21,7 @@ class ClapperInterface extends Gtk.Grid
|
|||||||
this.controlsInVideo = false;
|
this.controlsInVideo = false;
|
||||||
this.lastVolumeValue = null;
|
this.lastVolumeValue = null;
|
||||||
this.lastPositionValue = 0;
|
this.lastPositionValue = 0;
|
||||||
|
this.needsTracksUpdate = true;
|
||||||
this.revealTime = 800;
|
this.revealTime = 800;
|
||||||
|
|
||||||
this.overlay = new Gtk.Overlay();
|
this.overlay = new Gtk.Overlay();
|
||||||
@@ -60,6 +61,9 @@ class ClapperInterface extends Gtk.Grid
|
|||||||
this.controls.connect(
|
this.controls.connect(
|
||||||
'position-seeking-changed', this._onPositionSeekingChanged.bind(this)
|
'position-seeking-changed', this._onPositionSeekingChanged.bind(this)
|
||||||
);
|
);
|
||||||
|
this.controls.connect(
|
||||||
|
'track-change-requested', this._onTrackChangeRequested.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
this.overlay.add(this._player.widget);
|
this.overlay.add(this._player.widget);
|
||||||
}
|
}
|
||||||
@@ -98,17 +102,136 @@ class ClapperInterface extends Gtk.Grid
|
|||||||
debug(`placed controls in overlay: ${isOnVideo}`);
|
debug(`placed controls in overlay: ${isOnVideo}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateMediaTracks()
|
||||||
|
{
|
||||||
|
let mediaInfo = this._player.get_media_info();
|
||||||
|
|
||||||
|
// titlebar from video title should be set from this (not implemented yet)
|
||||||
|
//let title = mediaInfo.get_title();
|
||||||
|
|
||||||
|
// we can also check if video is "live" or "seekable" (right now unused)
|
||||||
|
// it might be a good idea to hide position seek bar and disable seeking
|
||||||
|
// when playing not seekable media (not implemented yet)
|
||||||
|
//let isLive = mediaInfo.is_live();
|
||||||
|
//let isSeekable = mediaInfo.is_seekable();
|
||||||
|
|
||||||
|
let streamList = mediaInfo.get_stream_list();
|
||||||
|
let parsedInfo = {
|
||||||
|
videoTracks: [],
|
||||||
|
audioTracks: [],
|
||||||
|
subtitleTracks: []
|
||||||
|
};
|
||||||
|
|
||||||
|
for(let info of streamList) {
|
||||||
|
let type, text;
|
||||||
|
|
||||||
|
switch(info.constructor) {
|
||||||
|
case GstPlayer.PlayerVideoInfo:
|
||||||
|
type = 'video';
|
||||||
|
let fps = info.get_framerate();
|
||||||
|
text = info.get_codec() + ', ' +
|
||||||
|
+ info.get_width() + 'x'
|
||||||
|
+ info.get_height() + '@'
|
||||||
|
+ Number((fps[0] / fps[1]).toFixed(2));
|
||||||
|
break;
|
||||||
|
case GstPlayer.PlayerAudioInfo:
|
||||||
|
type = 'audio';
|
||||||
|
let codec = info.get_codec();
|
||||||
|
// This one is too long to fit nicely in UI
|
||||||
|
if(codec.startsWith('Free Lossless Audio Codec'))
|
||||||
|
codec = 'FLAC';
|
||||||
|
text = info.get_language() + ', '
|
||||||
|
+ codec + ', '
|
||||||
|
+ info.get_channels() + ' Channels';
|
||||||
|
break;
|
||||||
|
case GstPlayer.PlayerSubtitleInfo:
|
||||||
|
type = 'subtitle';
|
||||||
|
text = info.get_language();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
debug(`unrecognized media info type: ${info.constructor}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let tracksArr = parsedInfo[`${type}Tracks`];
|
||||||
|
if(!tracksArr.length)
|
||||||
|
{
|
||||||
|
tracksArr[0] = {
|
||||||
|
label: 'Disabled',
|
||||||
|
type: type,
|
||||||
|
value: -1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
tracksArr.push({
|
||||||
|
label: text,
|
||||||
|
type: type,
|
||||||
|
value: info.get_index(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let type of ['video', 'audio', 'subtitle']) {
|
||||||
|
let currStream = this._player[`get_current_${type}_track`]();
|
||||||
|
let activeId = (currStream) ? currStream.get_index() : -1;
|
||||||
|
|
||||||
|
if(currStream && type !== 'subtitle') {
|
||||||
|
let caps = currStream.get_caps();
|
||||||
|
debug(`${type} caps: ${caps.to_string()}`, 'LEVEL_INFO');
|
||||||
|
}
|
||||||
|
this.controls.addRadioButtons(
|
||||||
|
this.controls[`${type}TracksButton`].popoverBox,
|
||||||
|
parsedInfo[`${type}Tracks`],
|
||||||
|
activeId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onTrackChangeRequested(self, trackType, trackId)
|
||||||
|
{
|
||||||
|
// reenabling audio is slow (as expected),
|
||||||
|
// so it is better to toggle mute instead
|
||||||
|
if(trackType === 'audio') {
|
||||||
|
if(trackId < 0)
|
||||||
|
return this._player.set_mute(true);
|
||||||
|
|
||||||
|
if(this._player.get_mute())
|
||||||
|
this._player.set_mute(false);
|
||||||
|
|
||||||
|
return this._player[`set_${trackType}_track`](trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(trackId < 0) {
|
||||||
|
// disabling video leaves last frame frozen,
|
||||||
|
// so we also hide the widget
|
||||||
|
if(trackType === 'video')
|
||||||
|
this._player.widget.hide();
|
||||||
|
|
||||||
|
return this._player[`set_${trackType}_track_enabled`](false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._player[`set_${trackType}_track`](trackId);
|
||||||
|
this._player[`set_${trackType}_track_enabled`](true);
|
||||||
|
|
||||||
|
if(trackType === 'video' && !this._player.widget.get_visible()) {
|
||||||
|
this._player.widget.show();
|
||||||
|
this._player.renderer.expose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onPlayerStateChanged(player, state)
|
_onPlayerStateChanged(player, state)
|
||||||
{
|
{
|
||||||
switch(state) {
|
switch(state) {
|
||||||
case GstPlayer.PlayerState.BUFFERING:
|
case GstPlayer.PlayerState.BUFFERING:
|
||||||
break;
|
break;
|
||||||
case GstPlayer.PlayerState.PAUSED:
|
|
||||||
case GstPlayer.PlayerState.STOPPED:
|
case GstPlayer.PlayerState.STOPPED:
|
||||||
|
this.needsTracksUpdate = true;
|
||||||
|
case GstPlayer.PlayerState.PAUSED:
|
||||||
this.controls.togglePlayButton.image = this.controls.playImage;
|
this.controls.togglePlayButton.image = this.controls.playImage;
|
||||||
break;
|
break;
|
||||||
case GstPlayer.PlayerState.PLAYING:
|
case GstPlayer.PlayerState.PLAYING:
|
||||||
this.controls.togglePlayButton.image = this.controls.pauseImage;
|
this.controls.togglePlayButton.image = this.controls.pauseImage;
|
||||||
|
if(this.needsTracksUpdate) {
|
||||||
|
this.needsTracksUpdate = false;
|
||||||
|
this.updateMediaTracks();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
Reference in New Issue
Block a user