lib: Introduce Clapper playback library

An easy to use media playback library (libclapper) as a GstPlayer replacement.

Previously we tried to use upstream `gstplayer` library to control playback and
pass all events from multiple threads GStreamer uses into an app main thread.
Since this caused some thread racy problems and we needed additional ABI breaking
changes to better suit our needs, we ended up with a modified fork of said library
renamed to `gstclapper` as a temporary solution.

This new library simply named `clapper` replaces our previous `gstclapper` solution
and is written completely from scratch by myself. The aim here is to have an easy to
use playback library better suited to work with (but not limited to) GTK and GObject
properties bindings by relying on "notify" signals.

Major differences include:
* Operates on a playback queue (inherits `GListModel` interface) instead of a single URI
* Uses "notify" signals for property changes always dispatched to app thread
* Time is passed/read as decimal number in seconds instead of int64 in nanoseconds
* Integrates `GstDiscoverer` to figure out media info (such as title) before playback
* Easy to use MPRIS support as part of library
* Optional playback remote controls with WebSocket messages

The new library will be distributed with Clapper player. This includes public headers
and GObject Introspection support.

Licensed under LGPL-2.1-or-later.

Enjoy
This commit is contained in:
Rafał Dzięgiel
2024-03-13 20:45:03 +01:00
parent edaba00658
commit d7f069d6c3
196 changed files with 17622 additions and 21004 deletions

View File

@@ -1,111 +0,0 @@
const { Gtk } = imports.gi;
const Dialogs = imports.src.dialogs;
const Prefs = imports.src.prefs;
const Misc = imports.src.misc;
var actions = {
open_local: ['<Ctrl>O'],
export_playlist: ['<Ctrl>E'],
open_uri: ['<Ctrl>U'],
prefs: ['<Ctrl>comma'],
shortcuts: ['F1', '<Ctrl>question'],
about: null,
progress_forward: ['Right'],
progress_backward: ['Left'],
next_chapter: ['<Shift>Right'],
prev_chapter: ['<Shift>Left'],
next_track: ['<Ctrl>Right'],
prev_track: ['<Ctrl>Left'],
volume_up: ['Up'],
volume_down: ['Down'],
mute: ['<Ctrl>M', 'M'],
toggle_play: ['space'],
change_repeat: ['<Ctrl>R'],
reveal_controls: ['Return'],
toggle_fullscreen: ['F11', 'F'],
leave_fullscreen: ['Escape'],
quit: ['<Ctrl>Q', 'Q'],
};
function handleAction(action, window)
{
const clapperWidget = window.child;
if(!clapperWidget) return;
const { player } = clapperWidget;
let bool = false;
switch(action.name) {
case 'open_local':
case 'export_playlist':
new Dialogs.FileChooser(window, action.name);
break;
case 'open_uri':
new Dialogs.UriDialog(window);
break;
case 'prefs':
new Prefs.PrefsWindow(window);
break;
case 'shortcuts':
if(!window.get_help_overlay()) {
const helpBuilder = Misc.getBuilderForName('help-overlay.ui');
window.set_help_overlay(helpBuilder.get_object('help_overlay'));
}
clapperWidget.activate_action('win.show-help-overlay', null);
break;
case 'about':
new Dialogs.AboutDialog(window);
break;
case 'progress_forward':
bool = true;
case 'progress_backward':
player.adjust_position(bool);
if(
clapperWidget.isReleaseKeyEnabled
&& clapperWidget.isFullscreenMode
)
clapperWidget.revealControls();
/* Actual seek is handled on release */
clapperWidget.isReleaseKeyEnabled = true;
if(!clapperWidget.has_focus)
clapperWidget.grab_focus();
break;
case 'volume_up':
bool = true;
case 'volume_down':
player.adjust_volume(bool);
break;
case 'mute':
player.mute ^= true;
break;
case 'next_track':
player.playlistWidget.nextTrack();
break;
case 'prev_track':
player.playlistWidget.prevTrack();
break;
case 'reveal_controls':
if(clapperWidget.isFullscreenMode)
clapperWidget.revealControls();
break;
case 'leave_fullscreen':
if(!clapperWidget.isFullscreenMode)
break;
case 'toggle_fullscreen':
clapperWidget.toggleFullscreen();
break;
case 'change_repeat':
player.playlistWidget.changeRepeatMode();
break;
case 'quit':
clapperWidget.activate_action('window.close', null);
break;
case 'toggle_play':
case 'next_chapter':
case 'prev_chapter':
player[action.name]();
break;
default:
break;
}
}

View File

@@ -1,126 +0,0 @@
const { Gio, GObject, Gtk } = imports.gi;
const { Widget } = imports.src.widget;
const Debug = imports.src.debug;
const FileOps = imports.src.fileOps;
const Misc = imports.src.misc;
const Actions = imports.src.actions;
const { debug } = Debug;
const { settings } = Misc;
var App = GObject.registerClass({
GTypeName: 'ClapperApp',
},
class ClapperApp extends Gtk.Application
{
_init()
{
super._init({
application_id: Misc.appId,
flags: Gio.ApplicationFlags.HANDLES_OPEN,
});
this.doneFirstActivate = false;
this.isFileAppend = false;
this.mapSignal = null;
}
vfunc_open(files, hint)
{
super.vfunc_open(files, hint);
this.activate();
this._openFilesAsync(files).catch(debug);
}
vfunc_activate()
{
super.vfunc_activate();
if(!this.doneFirstActivate)
this._onFirstActivate();
this.active_window.present();
}
async _openFilesAsync(files)
{
const urisArr = [];
for(let file of files) {
const uri = file.get_uri();
if(!uri.startsWith('file:')) {
urisArr.push(uri);
continue;
}
/* If file is not a dir its URI will be returned in an array */
const uris = await FileOps.getDirFilesUrisPromise(file).catch(debug);
if(uris && uris.length)
urisArr.push(...uris);
}
const [playlist, subs] = Misc.parsePlaylistFiles(urisArr);
const { player } = this.active_window.get_child();
const action = (this.isFileAppend) ? 'append' : 'set';
if(playlist && playlist.length)
player[`${action}_playlist`](playlist);
if(subs)
player.set_subtitles(subs);
/* Restore default behavior */
this.isFileAppend = false;
}
_onFirstActivate()
{
const window = new Gtk.ApplicationWindow({
application: this,
title: Misc.appName,
});
window.add_css_class('adwrounded');
if(!settings.get_boolean('render-shadows'))
window.add_css_class('gpufriendly');
window.add_css_class('gpufriendlyfs');
const clapperWidget = new Widget();
const dummyHeaderbar = new Gtk.Box({
can_focus: false,
focusable: false,
visible: false,
});
window.add_css_class('nobackground');
window.set_child(clapperWidget);
window.set_titlebar(dummyHeaderbar);
for(let name in Actions.actions) {
const simpleAction = new Gio.SimpleAction({ name });
simpleAction.connect('activate', (action) =>
Actions.handleAction(action, window)
);
this.add_action(simpleAction);
const accels = Actions.actions[name];
if(accels)
this.set_accels_for_action(`app.${name}`, accels);
}
this.mapSignal = window.connect('map', this._onWindowMap.bind(this));
this.doneFirstActivate = true;
}
_onWindowMap(window)
{
window.disconnect(this.mapSignal);
this.mapSignal = null;
debug('window mapped');
window.child._onWindowMap(window);
}
});

View File

@@ -1,260 +0,0 @@
const { GObject, Gtk } = imports.gi;
const Misc = imports.src.misc;
var CustomButton = GObject.registerClass({
GTypeName: 'ClapperCustomButton',
},
class ClapperCustomButton extends Gtk.Button
{
_init(opts)
{
opts = opts || {};
const defaults = {
halign: Gtk.Align.CENTER,
valign: Gtk.Align.CENTER,
can_focus: false,
};
Object.assign(opts, defaults);
super._init(opts);
this.add_css_class('flat');
this.add_css_class('clappercontrolsbutton');
}
vfunc_clicked()
{
const clapperWidget = this.get_ancestor(Gtk.Grid);
if(clapperWidget.isFullscreenMode)
clapperWidget.revealControls();
}
});
var IconToggleButton = GObject.registerClass({
GTypeName: 'ClapperIconToggleButton',
},
class ClapperIconToggleButton extends CustomButton
{
_init(primaryIcon, secondaryIcon)
{
super._init({
icon_name: primaryIcon,
});
this.primaryIcon = primaryIcon;
this.secondaryIcon = secondaryIcon;
}
setPrimaryIcon()
{
this.icon_name = this.primaryIcon;
}
setSecondaryIcon()
{
this.icon_name = this.secondaryIcon;
}
});
var PopoverSeparator = GObject.registerClass({
GTypeName: 'ClapperPopoverSeparator',
Template: Misc.getResourceUri('ui/popover-separator.ui'),
InternalChildren: ['middle_label'],
Properties: {
'label': GObject.ParamSpec.string(
'label',
'Middle label',
'Text to set in the middle',
GObject.ParamFlags.WRITABLE,
null
),
}
},
class ClapperPopoverSeparator extends Gtk.Box
{
_init(opts)
{
super._init();
if(!opts.label)
this.visible = false;
this.label = opts.label;
}
set label(value)
{
this._middle_label.label = value || "";
if(value)
this.visible = true;
}
});
var PopoverButtonBase = GObject.registerClass({
GTypeName: 'ClapperPopoverButtonBase',
},
class ClapperPopoverButtonBase extends Gtk.MenuButton
{
_init(opts = {})
{
super._init(opts);
if(opts.icon_name)
this.icon_name = opts.icon_name;
else if(opts.label)
this.label = opts.label;
this.toggleButton = this.get_first_child();
this.toggleButton.add_css_class('clappercontrolsbutton');
this.set_create_popup_func(this._onPopoverOpened);
this.popover.connect('closed', this._onPopoverClosed.bind(this));
}
_onPopoverOpened(self)
{
const clapperWidget = self.get_ancestor(Gtk.Grid);
if(clapperWidget.isFullscreenMode) {
clapperWidget.revealControls();
clapperWidget.isPopoverOpen = true;
}
}
_onPopoverClosed(popover)
{
const clapperWidget = this.get_ancestor(Gtk.Grid);
/* Set again timeout as popover is now closed */
if(clapperWidget.isFullscreenMode)
clapperWidget.revealControls();
clapperWidget.isPopoverOpen = false;
}
});
var ElapsedTimeButton = GObject.registerClass({
GTypeName: 'ClapperElapsedTimeButton',
Template: Misc.getResourceUri('ui/elapsed-time-button.ui'),
Children: ['scrolledWindow', 'speedScale'],
},
class ClapperElapsedTimeButton extends PopoverButtonBase
{
_init(opts)
{
super._init(opts);
this.setInitialState();
this.popover.add_css_class('elapsedpopover');
this.scrolledWindow.max_content_height = 150;
}
set label(value)
{
this.toggleButton.label = value;
}
get label()
{
return this.toggleButton.label;
}
setInitialState()
{
this.label = `00${Misc.timeColon}0000${Misc.timeColon}00`;
}
setFullscreenMode(isFullscreen, isMobileMonitor)
{
this.scrolledWindow.max_content_height = (isFullscreen && !isMobileMonitor)
? 190 : 150;
}
});
var TrackSelectButton = GObject.registerClass({
GTypeName: 'ClapperTrackSelectButton',
Template: Misc.getResourceUri('ui/track-select-button.ui'),
Children: ['popoverBox'],
InternalChildren: ['scrolled_window', 'decoder_separator'],
},
class ClapperTrackSelectButton extends PopoverButtonBase
{
_init(opts)
{
super._init(opts);
this._scrolled_window.max_content_height = 220;
}
setFullscreenMode(isFullscreen, isMobileMonitor)
{
this._scrolled_window.max_content_height = (isFullscreen && !isMobileMonitor)
? 290 : 220;
}
setDecoder(decoder)
{
this._decoder_separator.label = _('Decoder: %s').format(decoder);
}
});
var VolumeButton = GObject.registerClass({
GTypeName: 'ClapperVolumeButton',
Template: Misc.getResourceUri('ui/volume-button.ui'),
Children: ['volumeScale'],
Properties: {
'muted': GObject.ParamSpec.boolean(
'muted',
'Set muted',
'Mark scale as muted',
GObject.ParamFlags.WRITABLE,
false
),
}
},
class ClapperVolumeButton extends PopoverButtonBase
{
_init(opts)
{
super._init(opts);
this._isMuted = false;
}
set muted(isMuted)
{
this._isMuted = isMuted;
this._onVolumeScaleValueChanged(this.volumeScale);
}
_onVolumeScaleValueChanged(scale)
{
const volume = scale.get_value();
const cssClass = 'overamp';
const hasOveramp = (scale.has_css_class(cssClass));
if(volume > 1) {
if(!hasOveramp)
scale.add_css_class(cssClass);
}
else {
if(hasOveramp)
scale.remove_css_class(cssClass);
}
const icon = (volume <= 0 || this._isMuted)
? 'muted'
: (volume <= 0.3)
? 'low'
: (volume <= 0.7)
? 'medium'
: (volume <= 1)
? 'high'
: 'overamplified';
this.icon_name = `audio-volume-${icon}-symbolic`;
}
});

493
src/controls.js vendored
View File

@@ -1,493 +0,0 @@
const { GLib, GObject, Gdk, Gtk } = imports.gi;
const Buttons = imports.src.buttons;
const Debug = imports.src.debug;
const Misc = imports.src.misc;
const Revealers = imports.src.revealers;
const { debug } = Debug;
const { settings } = Misc;
var Controls = GObject.registerClass({
GTypeName: 'ClapperControls',
},
class ClapperControls extends Gtk.Box
{
_init()
{
super._init({
orientation: Gtk.Orientation.HORIZONTAL,
valign: Gtk.Align.END,
can_focus: false,
});
this.minFullViewWidth = 560;
this.currentPosition = 0;
this.isPositionDragging = false;
this.isMobile = false;
this.showHours = false;
this.durationFormatted = `00${Misc.timeColon}00`;
this.revealersArr = [];
this.chapters = null;
this.chapterShowId = null;
this.chapterHideId = null;
this.togglePlayButton = new Buttons.IconToggleButton(
'play-symbolic',
'pause-symbolic'
);
this.togglePlayButton.connect(
'clicked', this._onTogglePlayClicked.bind(this)
);
this.append(this.togglePlayButton);
const elapsedRevealer = new Revealers.ButtonsRevealer('SLIDE_RIGHT');
this.elapsedButton = new Buttons.ElapsedTimeButton();
elapsedRevealer.append(this.elapsedButton);
elapsedRevealer.reveal_child = true;
this.append(elapsedRevealer);
this.revealersArr.push(elapsedRevealer);
this._addPositionScale();
const revealTracksButton = new Buttons.IconToggleButton(
'go-previous-symbolic',
'go-next-symbolic'
);
revealTracksButton.add_css_class('narrowbutton');
const tracksRevealer = new Revealers.ButtonsRevealer(
'SLIDE_LEFT', revealTracksButton
);
this.visualizationsButton = new Buttons.TrackSelectButton({
icon_name: 'display-projector-symbolic',
visible: false,
});
tracksRevealer.append(this.visualizationsButton);
this.videoTracksButton = new Buttons.TrackSelectButton({
icon_name: 'emblem-videos-symbolic',
visible: false,
});
tracksRevealer.append(this.videoTracksButton);
this.audioTracksButton = new Buttons.TrackSelectButton({
icon_name: 'emblem-music-symbolic',
visible: false,
});
tracksRevealer.append(this.audioTracksButton);
this.subtitleTracksButton = new Buttons.TrackSelectButton({
icon_name: 'media-view-subtitles-symbolic',
visible: false,
});
tracksRevealer.append(this.subtitleTracksButton);
this.revealTracksRevealer = new Revealers.ButtonsRevealer('SLIDE_LEFT');
this.revealTracksRevealer.append(revealTracksButton);
this.revealTracksRevealer.set_visible(false);
this.append(this.revealTracksRevealer);
tracksRevealer.set_reveal_child(true);
this.revealersArr.push(tracksRevealer);
this.append(tracksRevealer);
this.volumeButton = new Buttons.VolumeButton();
this.append(this.volumeButton);
this.unfullscreenButton = new Buttons.CustomButton({
icon_name: 'view-restore-symbolic',
});
this.unfullscreenButton.connect('clicked', this._onUnfullscreenClicked.bind(this));
this.unfullscreenButton.set_visible(false);
this.append(this.unfullscreenButton);
this.add_css_class('clappercontrols');
this.realizeSignal = this.connect('realize', this._onRealize.bind(this));
}
setFullscreenMode(isFullscreen, isMobileMonitor)
{
/* Allow recheck on next resize */
this.isMobile = null;
this.elapsedButton.setFullscreenMode(isFullscreen, isMobileMonitor);
this.visualizationsButton.setFullscreenMode(isFullscreen, isMobileMonitor);
this.videoTracksButton.setFullscreenMode(isFullscreen, isMobileMonitor);
this.audioTracksButton.setFullscreenMode(isFullscreen, isMobileMonitor);
this.subtitleTracksButton.setFullscreenMode(isFullscreen, isMobileMonitor);
this.unfullscreenButton.visible = isFullscreen;
}
setLiveMode(isLive, isSeekable)
{
if(isLive)
this.elapsedButton.label = 'LIVE';
this.positionScale.visible = isSeekable;
}
setInitialState()
{
this.currentPosition = 0;
this.positionScale.set_value(0);
this.positionScale.visible = false;
this.elapsedButton.setInitialState();
this.togglePlayButton.setPrimaryIcon();
for(let type of ['video', 'audio', 'subtitle'])
this[`${type}TracksButton`].visible = false;
this.visualizationsButton.visible = false;
}
updateElapsedLabel(value)
{
value = value || 0;
const elapsed = Misc.getFormattedTime(value, this.showHours)
+ '' + this.durationFormatted;
this.elapsedButton.label = elapsed;
}
addCheckButtons(box, array, activeId)
{
let group = null;
let child = box.get_first_child();
let i = 0;
while(child || i < array.length) {
if(i >= array.length) {
if(child.visible) {
debug(`hiding unused ${child.type} checkButton nr: ${i}`);
child.visible = false;
}
i++;
child = child.get_next_sibling();
continue;
}
const el = array[i];
let checkButton;
if(child) {
checkButton = child;
debug(`reusing ${el.type} checkButton nr: ${i}`);
}
else {
debug(`creating new ${el.type} checkButton nr: ${i}`);
checkButton = new Gtk.CheckButton({
group: group,
});
checkButton.connect(
'toggled',
this._onCheckButtonToggled.bind(this)
);
box.append(checkButton);
}
checkButton.label = el.label;
debug(`checkButton label: ${checkButton.label}`);
checkButton.type = el.type;
debug(`checkButton type: ${checkButton.type}`);
checkButton.activeId = el.activeId;
debug(`checkButton id: ${checkButton.activeId}`);
checkButton.visible = true;
if(checkButton.activeId === activeId) {
checkButton.set_active(true);
debug(`activated ${el.type} checkButton nr: ${i}`);
}
if(!group)
group = checkButton;
i++;
if(child)
child = child.get_next_sibling();
}
}
_handleTrackChange(checkButton)
{
const clapperWidget = this.get_ancestor(Gtk.Grid);
if(checkButton.activeId < 0) {
return clapperWidget.player[
`set_${checkButton.type}_track_enabled`
](false);
}
const setTrack = `set_${checkButton.type}_track`;
clapperWidget.player[setTrack](checkButton.activeId);
clapperWidget.player[`${setTrack}_enabled`](true);
}
_handleVisualizationChange(checkButton)
{
const clapperWidget = this.get_ancestor(Gtk.Grid);
const isEnabled = clapperWidget.player.get_visualization_enabled();
if(!checkButton.activeId) {
if(isEnabled) {
clapperWidget.player.set_visualization_enabled(false);
debug('disabled visualizations');
}
return;
}
const currVis = clapperWidget.player.get_current_visualization();
if(currVis === checkButton.activeId)
return;
debug(`set visualization: ${checkButton.activeId}`);
clapperWidget.player.set_visualization(checkButton.activeId);
if(!isEnabled) {
clapperWidget.player.set_visualization_enabled(true);
debug('enabled visualizations');
}
}
_addPositionScale()
{
this.positionScale = new Gtk.Scale({
orientation: Gtk.Orientation.HORIZONTAL,
value_pos: Gtk.PositionType.LEFT,
draw_value: false,
hexpand: true,
valign: Gtk.Align.CENTER,
can_focus: false,
visible: false,
});
const scrollController = new Gtk.EventControllerScroll();
scrollController.set_flags(Gtk.EventControllerScrollFlags.BOTH_AXES);
scrollController.connect('scroll', this._onPositionScaleScroll.bind(this));
this.positionScale.add_controller(scrollController);
this.positionScale.add_css_class('positionscale');
this.positionScaleValueSignal = this.positionScale.connect(
'value-changed', this._onPositionScaleValueChanged.bind(this)
);
/* GTK4 is missing pressed/released signals for GtkRange/GtkScale.
* We cannot add controllers, cause it already has them, so we
* workaround this by observing css classes it currently has */
this.positionScaleDragSignal = this.positionScale.connect(
'notify::css-classes', this._onPositionScaleDragging.bind(this)
);
this.positionAdjustment = this.positionScale.get_adjustment();
this.positionAdjustment.set_page_increment(0);
this.positionAdjustment.set_step_increment(8);
const chapterLabel = new Gtk.Label();
chapterLabel.add_css_class('chapterlabel');
this.chapterPopover = new Gtk.Popover({
position: Gtk.PositionType.TOP,
autohide: false,
child: chapterLabel,
});
const box = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
hexpand: true,
valign: Gtk.Align.CENTER,
can_focus: false,
});
box.append(this.chapterPopover);
box.append(this.positionScale);
this.append(box);
}
_setChapterVisible(isVisible)
{
const type = (isVisible) ? 'Show' : 'Hide';
const anti = (isVisible) ? 'Hide' : 'Show';
if(this[`chapter${anti}Id`]) {
GLib.source_remove(this[`chapter${anti}Id`]);
this[`chapter${anti}Id`] = null;
}
if(
this[`chapter${type}Id`]
|| (!isVisible && this.chapterPopover.visible === isVisible)
)
return;
debug(`changing chapter visibility to: ${isVisible}`);
this[`chapter${type}Id`] = GLib.idle_add(
GLib.PRIORITY_DEFAULT_IDLE + 20,
() => {
if(isVisible) {
const [start, end] = this.positionScale.get_slider_range();
const controlsHeight = this.parent.get_height();
const scaleBoxHeight = this.positionScale.parent.get_height();
const [isShared, destX, destY] = this.positionScale.translate_coordinates(
this.positionScale.parent, 0, 0
);
const clapperWidget = this.get_ancestor(Gtk.Grid);
/* Half of slider width, values are defined in CSS */
const sliderOffset = (
clapperWidget.isFullscreenMode && !clapperWidget.isMobileMonitor
) ? 10 : 9;
this.chapterPopover.set_pointing_to(new Gdk.Rectangle({
x: destX + end - sliderOffset,
y: -(controlsHeight - scaleBoxHeight) / 2,
width: 0,
height: 0,
}));
}
this.chapterPopover.visible = isVisible;
this[`chapter${type}Id`] = null;
debug(`chapter visible: ${isVisible}`);
return GLib.SOURCE_REMOVE;
}
);
}
_onRealize()
{
this.disconnect(this.realizeSignal);
this.realizeSignal = null;
const clapperWidget = this.get_ancestor(Gtk.Grid);
const scrollController = new Gtk.EventControllerScroll();
scrollController.set_flags(
Gtk.EventControllerScrollFlags.VERTICAL
| Gtk.EventControllerScrollFlags.DISCRETE
);
scrollController.connect('scroll', clapperWidget._onScroll.bind(clapperWidget));
this.volumeButton.add_controller(scrollController);
const initialVolume = (settings.get_boolean('volume-custom'))
? settings.get_int('volume-value') / 100
: settings.get_double('volume-last');
clapperWidget.player.volume = initialVolume;
clapperWidget.player.bind_property('mute', this.volumeButton, 'muted',
GObject.BindingFlags.DEFAULT
);
}
_onPlayerResize(width, height)
{
const isMobile = (width < this.minFullViewWidth);
if(this.isMobile === isMobile)
return;
for(let revealer of this.revealersArr)
revealer.set_reveal_child(!isMobile);
this.revealTracksRevealer.set_reveal_child(isMobile);
this.isMobile = isMobile;
}
_onUnfullscreenClicked(button)
{
const root = button.get_root();
root.unfullscreen();
}
_onCheckButtonToggled(checkButton)
{
if(!checkButton.get_active())
return;
switch(checkButton.type) {
case 'video':
case 'audio':
case 'subtitle':
this._handleTrackChange(checkButton);
break;
case 'visualization':
this._handleVisualizationChange(checkButton);
break;
default:
break;
}
}
_onTogglePlayClicked()
{
/* Parent of controls changes, so get ancestor instead */
const { player } = this.get_ancestor(Gtk.Grid);
player.toggle_play();
}
_onPositionScaleScroll(controller, dx, dy)
{
const clapperWidget = this.get_ancestor(Gtk.Grid);
clapperWidget._onScroll(controller, dx || dy, 0);
}
_onPositionScaleValueChanged(scale)
{
const scaleValue = scale.get_value();
const positionSeconds = Math.round(scaleValue);
this.currentPosition = positionSeconds;
this.updateElapsedLabel(positionSeconds);
if(this.chapters && this.isPositionDragging) {
const chapter = this.chapters[scaleValue];
const isChapter = (chapter != null);
if(isChapter)
this.chapterPopover.child.label = chapter;
this._setChapterVisible(isChapter);
}
}
_onPositionScaleDragging(scale)
{
const isPositionDragging = scale.has_css_class('dragging');
if(this.isPositionDragging === isPositionDragging)
return;
const clapperWidget = this.get_ancestor(Gtk.Grid);
if(!clapperWidget) return;
if(clapperWidget.isFullscreenMode) {
clapperWidget.revealControls();
if(isPositionDragging)
clapperWidget._clearTimeout('hideControls');
}
if((this.isPositionDragging = isPositionDragging))
return;
const scaleValue = scale.get_value();
const isChapterSeek = this.chapterPopover.visible;
if(!isChapterSeek) {
const positionSeconds = Math.round(scaleValue);
clapperWidget.player.seek_seconds(positionSeconds);
}
else {
clapperWidget.player.seek_chapter(scaleValue);
this._setChapterVisible(false);
}
}
_onCloseRequest()
{
debug('controls close request');
this.positionScale.disconnect(this.positionScaleValueSignal);
this.positionScale.disconnect(this.positionScaleDragSignal);
}
});

View File

@@ -1,57 +0,0 @@
const { Gio } = imports.gi;
const Debug = imports.src.debug;
const { debug } = Debug;
const ShellProxyWrapper = Gio.DBusProxy.makeProxyWrapper(`
<node>
<interface name="org.gnome.Shell">
<method name="Eval">
<arg type="s" direction="in" name="script"/>
<arg type="b" direction="out" name="success"/>
<arg type="s" direction="out" name="result"/>
</method>
</interface>
</node>`
);
let shellProxy = null;
debug('creating GNOME Shell DBus proxy');
new ShellProxyWrapper(
Gio.DBus.session,
'org.gnome.Shell',
'/org/gnome/Shell',
(proxy, err) => {
if(err) {
debug(err);
return;
}
shellProxy = proxy;
debug('GNOME Shell DBus proxy is ready');
},
null,
Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION
| Gio.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS
);
function shellWindowEval(fn, isEnabled)
{
if(!shellProxy)
return;
const un = (isEnabled) ? '' : 'un';
debug(`changing ${fn}`);
shellProxy.EvalRemote(
`global.display.focus_window.${un}${fn}()`,
(out) => {
const debugMsg = (out[0])
? `window ${fn}: ${isEnabled}`
: new Error(out[1]);
debug(debugMsg);
}
);
}

View File

@@ -1,55 +0,0 @@
const { GLib } = imports.gi;
const { Debug } = imports.extras.debug;
const { Ink } = imports.extras.ink;
const G_DEBUG_ENV = GLib.getenv('G_MESSAGES_DEBUG');
const clapperDebugger = new Debug.Debugger('Clapper', {
name_printer: new Ink.Printer({
font: Ink.Font.BOLD,
color: Ink.Color.MAGENTA
}),
time_printer: new Ink.Printer({
color: Ink.Color.ORANGE
}),
high_precision: true,
});
clapperDebugger.enabled = (
clapperDebugger.enabled
|| G_DEBUG_ENV != null
&& G_DEBUG_ENV.includes('Clapper')
);
function _logStructured(debuggerName, msg, level)
{
GLib.log_structured(
debuggerName, level, {
MESSAGE: msg,
SYSLOG_IDENTIFIER: debuggerName.toLowerCase()
});
}
function _debug(debuggerName, msg)
{
if(msg.message) {
_logStructured(
debuggerName,
msg.message,
GLib.LogLevelFlags.LEVEL_CRITICAL
);
return;
}
clapperDebugger.debug(msg);
}
function debug(msg)
{
_debug('Clapper', msg);
}
function warn(msg)
{
_logStructured('Clapper', msg, GLib.LogLevelFlags.LEVEL_WARNING);
}

View File

@@ -1,314 +0,0 @@
const { Adw, Gdk, Gio, GObject, Gst, Gtk } = imports.gi;
const System = imports.system;
const Debug = imports.src.debug;
const FileOps = imports.src.fileOps;
const Misc = imports.src.misc;
const { debug } = Debug;
var FileChooser = GObject.registerClass({
GTypeName: 'ClapperFileChooser',
},
class ClapperFileChooser extends Gtk.FileChooserNative
{
_init(window, purpose)
{
super._init({
transient_for: window,
modal: true,
});
switch(purpose) {
case 'open_local':
this._prepareOpenLocal();
break;
case 'export_playlist':
this._prepareExportPlaylist();
break;
default:
debug(new Error(`unknown file chooser purpose: ${purpose}`));
break;
}
this.chooserPurpose = purpose;
/* File chooser closes itself when nobody is holding its ref */
this.ref();
this.show();
}
_prepareOpenLocal()
{
this.select_multiple = true;
const filter = new Gtk.FileFilter({
name: 'Media Files',
});
filter.add_mime_type('video/*');
filter.add_mime_type('audio/*');
filter.add_mime_type('application/claps');
Misc.subsMimes.forEach(mime => filter.add_mime_type(mime));
this.add_filter(filter);
}
_prepareExportPlaylist()
{
this.action = Gtk.FileChooserAction.SAVE;
this.set_current_name('playlist.claps');
const filter = new Gtk.FileFilter({
name: 'Playlist Files',
});
filter.add_mime_type('application/claps');
this.add_filter(filter);
}
vfunc_response(respId)
{
debug('closing file chooser dialog');
if(respId === Gtk.ResponseType.ACCEPT) {
switch(this.chooserPurpose) {
case 'open_local':
this._handleOpenLocal();
break;
case 'export_playlist':
this._handleExportPlaylist();
break;
}
}
this.unref();
this.destroy();
}
_handleOpenLocal()
{
const files = this.get_files();
const filesArray = [];
let index = 0;
let file;
while((file = files.get_item(index))) {
filesArray.push(file);
index++;
}
const { application } = this.transient_for;
const isHandlesOpen = Boolean(
application.flags & Gio.ApplicationFlags.HANDLES_OPEN
);
/* Remote app does not handle open */
if(isHandlesOpen)
application.open(filesArray, "");
else
application._openFilesAsync(filesArray);
}
_handleExportPlaylist()
{
const file = this.get_file();
const { playlistWidget } = this.transient_for.child.player;
const playlist = playlistWidget.getPlaylist(true);
FileOps.saveFileSimplePromise(file, playlist.join('\n'))
.then(() => {
debug(`exported playlist to file: ${file.get_path()}`);
})
.catch(err => {
debug(err);
});
}
});
var UriDialog = GObject.registerClass({
GTypeName: 'ClapperUriDialog',
},
class ClapperUriDialog extends Gtk.Dialog
{
_init(window)
{
super._init({
transient_for: window,
modal: true,
use_header_bar: true,
title: _('Open URI'),
default_width: 420,
});
const contentBox = this.get_content_area();
contentBox.vexpand = true;
contentBox.add_css_class('uridialogbox');
const linkEntry = new Gtk.Entry({
activates_default: true,
truncate_multiline: true,
height_request: 38,
hexpand: true,
valign: Gtk.Align.CENTER,
input_purpose: Gtk.InputPurpose.URL,
placeholder_text: _('Enter or drop URI here'),
});
linkEntry.connect('notify::text', this._onTextNotify.bind(this));
contentBox.append(linkEntry);
this.add_button(_('Cancel'), Gtk.ResponseType.CANCEL);
this.add_button(_('Open'), Gtk.ResponseType.OK);
this.set_default_response(Gtk.ResponseType.OK);
this.set_response_sensitive(Gtk.ResponseType.OK, false);
const display = Gdk.Display.get_default();
const clipboard = (display) ? display.get_clipboard() : null;
if(clipboard)
clipboard.read_text_async(null, this._readTextAsyncCb.bind(this));
this.show();
}
vfunc_response(respId)
{
if(respId === Gtk.ResponseType.OK) {
const contentBox = this.get_content_area();
const linkEntry = contentBox.get_last_child();
const { player } = this.transient_for.child;
player.set_playlist([linkEntry.text]);
}
this.destroy();
}
_onTextNotify(entry)
{
const isUriValid = (entry.text.length)
? Gst.uri_is_valid(entry.text)
: false;
this.set_response_sensitive(Gtk.ResponseType.OK, isUriValid);
}
_readTextAsyncCb(clipboard, result)
{
let uri = null;
try {
uri = clipboard.read_text_finish(result);
}
catch(err) {
debug(`could not read clipboard: ${err.message}`);
}
if(!uri || !Gst.uri_is_valid(uri))
return;
const contentBox = this.get_content_area();
const linkEntry = contentBox.get_last_child();
linkEntry.set_text(uri);
linkEntry.select_region(0, -1);
}
});
var ResumeDialog = GObject.registerClass({
GTypeName: 'ClapperResumeDialog',
},
class ClapperResumeDialog extends Gtk.MessageDialog
{
_init(window, resumeInfo)
{
const percentage = Math.round((resumeInfo.time / resumeInfo.duration) * 100);
const msg = [
'<b>' + _('Title') + `:</b> ${resumeInfo.title}`,
'<b>' + _('Completed') + `:</b> ${percentage}%`
].join('\n');
super._init({
transient_for: window,
modal: true,
message_type: Gtk.MessageType.QUESTION,
buttons: Gtk.ButtonsType.YES_NO,
text: _('Resume playback?'),
secondary_use_markup: true,
secondary_text: msg,
});
this.resumeInfo = resumeInfo;
this.set_default_response(Gtk.ResponseType.YES);
this.show();
}
vfunc_response(respId)
{
const { player } = this.transient_for.child;
if(respId === Gtk.ResponseType.YES)
player.seek_seconds(this.resumeInfo.time);
this.destroy();
}
});
var AboutDialog = GObject.registerClass({
GTypeName: 'ClapperAboutDialog',
},
class ClapperAboutDialog extends Gtk.AboutDialog
{
_init(window)
{
const gstVer = [
Gst.VERSION_MAJOR, Gst.VERSION_MINOR, Gst.VERSION_MICRO
].join('.');
const gtkVer = [
Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION, Gtk.MICRO_VERSION
].join('.');
/* TODO: This is as of Alpha2 still broken, requires:
* https://gitlab.gnome.org/GNOME/libadwaita/-/merge_requests/230
* can be simplified later in future */
const adwVer = Adw.MAJOR_VERSION ? [
Adw.MAJOR_VERSION, Adw.MINOR_VERSION, Adw.MICRO_VERSION
].join('.') : '1.0.0';
const gjsVerStr = String(System.version);
let gjsVer = '';
gjsVer += gjsVerStr.charAt(0) + '.';
gjsVer += gjsVerStr.charAt(1) + gjsVerStr.charAt(2) + '.';
if(gjsVerStr.charAt(3) !== '0')
gjsVer += gjsVerStr.charAt(3);
gjsVer += gjsVerStr.charAt(4);
const osInfo = [
_('GTK version: %s').format(gtkVer),
_('Adwaita version: %s').format(adwVer),
_('GStreamer version: %s').format(gstVer),
_('GJS version: %s').format(gjsVer)
].join('\n');
super._init({
transient_for: window,
destroy_with_parent: true,
modal: true,
program_name: Misc.appName,
comments: _('A GNOME media player powered by GStreamer'),
version: pkg.version,
authors: ['Rafał Dzięgiel'],
artists: ['Rafał Dzięgiel'],
/* TRANSLATORS: Put your name(s) here for credits or leave untranslated */
translator_credits: _('translator-credits'),
license_type: Gtk.License.GPL_3_0,
logo_icon_name: 'com.github.rafostar.Clapper',
website: 'https://rafostar.github.io/clapper',
system_information: osInfo,
});
this.show();
}
});

View File

@@ -1,227 +0,0 @@
const { Gio, GLib } = imports.gi;
const ByteArray = imports.byteArray;
const Debug = imports.src.debug;
const Misc = imports.src.misc;
const { debug } = Debug;
/* FIXME: Use Gio._LocalFilePrototype once we are safe to assume
* that GJS with https://gitlab.gnome.org/GNOME/gjs/-/commit/ec9385b8 is used. */
const LocalFilePrototype = Gio.File.new_for_path('/').constructor.prototype;
Gio._promisify(LocalFilePrototype, 'load_bytes_async', 'load_bytes_finish');
Gio._promisify(LocalFilePrototype, 'make_directory_async', 'make_directory_finish');
Gio._promisify(LocalFilePrototype, 'replace_contents_bytes_async', 'replace_contents_finish');
Gio._promisify(LocalFilePrototype, 'query_info_async', 'query_info_finish');
Gio._promisify(LocalFilePrototype, 'enumerate_children_async', 'enumerate_children_finish');
Gio._promisify(Gio.FileEnumerator.prototype, 'close_async', 'close_finish');
Gio._promisify(Gio.FileEnumerator.prototype, 'next_files_async', 'next_files_finish');
function createCacheDirPromise()
{
const dir = Gio.File.new_for_path(
GLib.get_user_cache_dir() + '/' + Misc.appId
);
return createDirPromise(dir);
}
function createTempDirPromise()
{
const dir = Gio.File.new_for_path(
GLib.get_tmp_dir() + '/' + Misc.appId
);
return createDirPromise(dir);
}
/* Creates dir and resolves with it */
function createDirPromise(dir)
{
return new Promise((resolve, reject) => {
if(dir.query_exists(null))
return resolve(dir);
dir.make_directory_async(
GLib.PRIORITY_DEFAULT,
null
)
.then(success => {
if(success)
return resolve(dir);
reject(new Error(`could not create dir: ${dir.get_path()}`));
})
.catch(err => reject(err));
});
}
/* Simple save data to GioFile */
function saveFileSimplePromise(file, data)
{
return file.replace_contents_bytes_async(
GLib.Bytes.new_take(data),
null,
false,
Gio.FileCreateFlags.NONE,
null
);
}
/* Saves file in optional subdirectory and resolves with it */
function saveFilePromise(place, subdirName, fileName, data)
{
return new Promise(async (resolve, reject) => {
let folderPath = GLib[`get_${place}_dir`]() + '/' + Misc.appId;
if(subdirName)
folderPath += `/${subdirName}`;
const destDir = Gio.File.new_for_path(folderPath);
const destPath = folderPath + '/' + fileName;
debug(`saving file: ${destPath}`);
const checkFolders = (subdirName)
? [destDir.get_parent(), destDir]
: [destDir];
for(let dir of checkFolders) {
const createdDir = await createDirPromise(dir).catch(debug);
if(!createdDir)
return reject(new Error(`could not create dir: ${dir.get_path()}`));
}
const destFile = destDir.get_child(fileName);
saveFileSimplePromise(destFile, data)
.then(() => {
debug(`saved file: ${destPath}`);
resolve(destFile);
})
.catch(err => reject(err));
});
}
function getFileContentsPromise(place, subdirName, fileName)
{
return new Promise((resolve, reject) => {
let destPath = GLib[`get_${place}_dir`]() + '/' + Misc.appId;
if(subdirName)
destPath += `/${subdirName}`;
destPath += `/${fileName}`;
const file = Gio.File.new_for_path(destPath);
debug(`reading data from: ${destPath}`);
if(!file.query_exists(null)) {
debug(`no such file: ${file.get_path()}`);
return resolve(null);
}
file.load_bytes_async(null)
.then(result => {
const data = result[0].get_data();
if(!data || !data.length)
return reject(new Error('source file is empty'));
debug(`read data from: ${destPath}`);
if(data instanceof Uint8Array)
resolve(ByteArray.toString(data));
else
resolve(data);
})
.catch(err => reject(err));
});
}
function _getDirUrisPromise(dir, isDeep)
{
return new Promise(async (resolve, reject) => {
const enumerator = await dir.enumerate_children_async(
'standard::name,standard::type',
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_DEFAULT,
null
).catch(debug);
if(!enumerator)
return reject(new Error('could not create file enumerator'));
const dirPath = dir.get_path();
const arr = [];
debug(`enumerating files in dir: ${dirPath}`);
while(true) {
const infos = await enumerator.next_files_async(
1,
GLib.PRIORITY_DEFAULT,
null
).catch(debug);
if(!infos || !infos.length)
break;
const fileUri = dir.get_uri() + '/' + infos[0].get_name();
if(infos[0].get_file_type() !== Gio.FileType.DIRECTORY) {
arr.push(fileUri);
continue;
}
if(!isDeep)
continue;
const subDir = Misc.getFileFromLocalUri(fileUri);
const subDirUris = await _getDirUrisPromise(subDir, isDeep).catch(debug);
if(subDirUris && subDirUris.length)
arr.push(...subDirUris);
}
const isClosed = await enumerator.close_async(
GLib.PRIORITY_DEFAULT,
null
).catch(debug);
if(isClosed)
debug(`closed enumerator for dir: ${dirPath}`);
else
debug(new Error(`could not close file enumerator for dir: ${dirPath}`));
resolve(arr);
});
}
/* Either GioFile or URI for dir arg */
function getDirFilesUrisPromise(dir, isDeep)
{
return new Promise(async (resolve, reject) => {
if(!dir.get_path)
dir = Misc.getFileFromLocalUri(dir);
if(!dir)
return reject(new Error('invalid directory'));
const fileInfo = await dir.query_info_async(
'standard::type',
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_DEFAULT,
null
).catch(debug);
if(!fileInfo)
return reject(new Error('no file type info'));
if(fileInfo.get_file_type() !== Gio.FileType.DIRECTORY)
return resolve([dir.get_uri()]);
const arr = await _getDirUrisPromise(dir, isDeep).catch(debug);
if(!arr || !arr.length)
return reject(new Error('enumerated files list is empty'));
resolve(arr.sort());
});
}

View File

@@ -1,299 +0,0 @@
const { GObject, Gtk } = imports.gi;
const Debug = imports.src.debug;
const Misc = imports.src.misc;
const { debug } = Debug;
var HeaderBar = GObject.registerClass({
GTypeName: 'ClapperHeaderBar',
},
class ClapperHeaderBar extends Gtk.Box
{
_init()
{
super._init({
can_focus: false,
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 6,
margin_top: 6,
margin_start: 6,
margin_end: 6,
});
this.add_css_class('osdheaderbar');
this.isMaximized = false;
this.isMenuOnLeft = true;
const uiBuilder = Misc.getBuilderForName('clapper.ui');
this.menuWidget = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
valign: Gtk.Align.CENTER,
spacing: 6,
});
this.menuButton = new Gtk.MenuButton({
icon_name: 'open-menu-symbolic',
valign: Gtk.Align.CENTER,
can_focus: false,
});
const menuToggleButton = this.menuButton.get_first_child();
menuToggleButton.add_css_class('osd');
const mainMenuModel = uiBuilder.get_object('mainMenu');
const mainMenuPopover = new HeaderBarPopover(mainMenuModel);
this.menuButton.set_popover(mainMenuPopover);
this.menuButton.add_css_class('circular');
this.menuWidget.append(this.menuButton);
this.extraButtonsBox = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
valign: Gtk.Align.CENTER,
});
this.extraButtonsBox.add_css_class('linked');
const floatButton = new Gtk.Button({
icon_name: 'pip-in-symbolic',
can_focus: false,
});
floatButton.add_css_class('osd');
floatButton.add_css_class('circular');
floatButton.add_css_class('linkedleft');
floatButton.connect('clicked',
this._onFloatButtonClicked.bind(this)
);
this.extraButtonsBox.append(floatButton);
const separator = new Gtk.Separator({
orientation: Gtk.Orientation.VERTICAL,
});
separator.add_css_class('linkseparator');
this.extraButtonsBox.append(separator);
const fullscreenButton = new Gtk.Button({
icon_name: 'view-fullscreen-symbolic',
can_focus: false,
});
fullscreenButton.add_css_class('osd');
fullscreenButton.add_css_class('circular');
fullscreenButton.add_css_class('linkedright');
fullscreenButton.connect('clicked',
this._onFullscreenButtonClicked.bind(this)
);
this.extraButtonsBox.append(fullscreenButton);
this.menuWidget.append(this.extraButtonsBox);
this.spacerWidget = new Gtk.Box({
hexpand: true,
});
this.minimizeWidget = this._getWindowButton('minimize');
this.maximizeWidget = this._getWindowButton('maximize');
this.closeWidget = this._getWindowButton('close');
const gtkSettings = Gtk.Settings.get_default();
this._onLayoutUpdate(gtkSettings);
gtkSettings.connect(
'notify::gtk-decoration-layout',
this._onLayoutUpdate.bind(this)
);
}
setMenuOnLeft(isOnLeft)
{
if(this.isMenuOnLeft === isOnLeft)
return;
if(isOnLeft) {
this.menuWidget.reorder_child_after(
this.extraButtonsBox, this.menuButton
);
}
else {
this.menuWidget.reorder_child_after(
this.menuButton, this.extraButtonsBox
);
}
this.isMenuOnLeft = isOnLeft;
}
setMaximized(isMaximized)
{
if(this.isMaximized === isMaximized)
return;
this.maximizeWidget.icon_name = (isMaximized)
? 'window-restore-symbolic'
: 'window-maximize-symbolic';
this.isMaximized = isMaximized;
}
_onLayoutUpdate(gtkSettings)
{
const gtkLayout = gtkSettings.gtk_decoration_layout;
this._replaceButtons(gtkLayout);
}
_replaceButtons(gtkLayout)
{
const modLayout = gtkLayout.replace(':', ',spacer,');
const layoutArr = modLayout.split(',');
let lastWidget = null;
let showMinimize = false;
let showMaximize = false;
let showClose = false;
let menuAdded = false;
let spacerAdded = false;
debug(`headerbar layout: ${modLayout}`);
for(let name of layoutArr) {
/* Menu might be named "appmenu" */
if(!menuAdded && (!name || name === 'appmenu' || name === 'icon'))
name = 'menu';
const widget = this[`${name}Widget`];
if(!widget) continue;
if(!widget.parent)
this.append(widget);
else
this.reorder_child_after(widget, lastWidget);
switch(name) {
case 'spacer':
spacerAdded = true;
break;
case 'minimize':
showMinimize = true;
break;
case 'maximize':
showMaximize = true;
break;
case 'close':
showClose = true;
break;
case 'menu':
this.setMenuOnLeft(!spacerAdded);
menuAdded = true;
break;
default:
break;
}
lastWidget = widget;
}
this.minimizeWidget.visible = showMinimize;
this.maximizeWidget.visible = showMaximize;
this.closeWidget.visible = showClose;
}
_getWindowButton(name)
{
const button = new Gtk.Button({
icon_name: `window-${name}-symbolic`,
valign: Gtk.Align.CENTER,
can_focus: false,
});
button.add_css_class('osd');
button.add_css_class('circular');
if(name === 'maximize')
name = 'toggle-maximized';
button.connect('clicked',
this._onWindowButtonActivate.bind(this, name)
);
return button;
}
_updateFloatIcon(isFloating)
{
const floatButton = this.extraButtonsBox.get_first_child();
if(!floatButton) return;
const iconName = (isFloating)
? 'pip-out-symbolic'
: 'pip-in-symbolic';
if(floatButton.icon_name !== iconName)
floatButton.icon_name = iconName;
}
_onWindowButtonActivate(action)
{
this.activate_action(`window.${action}`, null);
}
_onFloatButtonClicked(button)
{
const clapperWidget = this.root.child;
const { controlsRevealer } = clapperWidget;
controlsRevealer.toggleReveal();
/* Reset timer to not disappear during click */
clapperWidget._setHideControlsTimeout();
this._updateFloatIcon(!controlsRevealer.reveal_child);
}
_onFullscreenButtonClicked(button)
{
this.root.fullscreen();
}
});
var HeaderBarPopover = GObject.registerClass({
GTypeName: 'ClapperHeaderBarPopover',
},
class ClapperHeaderBarPopover extends Gtk.PopoverMenu
{
_init(model)
{
super._init({
menu_model: model,
});
this.connect('map', this._onMap.bind(this));
this.connect('closed', this._onClosed.bind(this));
}
_onMap()
{
const { child } = this.root;
if(
!child
|| !child.player
|| !child.player.widget
)
return;
child.revealControls();
child.isPopoverOpen = true;
}
_onClosed()
{
const { child } = this.root;
if(
!child
|| !child.player
|| !child.player.widget
)
return;
child.revealControls();
child.isPopoverOpen = false;
}
});

View File

@@ -0,0 +1,54 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_APP_BUS (clapper_app_bus_get_type())
#define CLAPPER_APP_BUS_CAST(obj) ((ClapperAppBus *)(obj))
/**
* ClapperAppBus:
*/
G_DECLARE_FINAL_TYPE (ClapperAppBus, clapper_app_bus, CLAPPER, APP_BUS, GstBus)
void clapper_app_bus_initialize (void);
ClapperAppBus * clapper_app_bus_new (void);
void clapper_app_bus_forward_message (ClapperAppBus *app_bus, GstMessage *msg);
void clapper_app_bus_post_prop_notify (ClapperAppBus *app_bus, GstObject *src, GParamSpec *pspec);
void clapper_app_bus_post_refresh_streams (ClapperAppBus *app_bus, GstObject *src);
void clapper_app_bus_post_refresh_timeline (ClapperAppBus *app_bus, GstObject *src);
void clapper_app_bus_post_simple_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id);
void clapper_app_bus_post_desc_with_details_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, const gchar *desc, const gchar *details);
void clapper_app_bus_post_error_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, GError *error, const gchar *debug_info);
G_END_DECLS

View File

@@ -0,0 +1,307 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <gio/gio.h>
#include "clapper-bus-private.h"
#include "clapper-app-bus-private.h"
#include "clapper-player-private.h"
#include "clapper-media-item-private.h"
#include "clapper-timeline-private.h"
#define GST_CAT_DEFAULT clapper_app_bus_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperAppBus
{
GstBus parent;
};
#define parent_class clapper_app_bus_parent_class
G_DEFINE_TYPE (ClapperAppBus, clapper_app_bus, GST_TYPE_BUS);
enum
{
CLAPPER_APP_BUS_STRUCTURE_UNKNOWN = 0,
CLAPPER_APP_BUS_STRUCTURE_PROP_NOTIFY,
CLAPPER_APP_BUS_STRUCTURE_REFRESH_STREAMS,
CLAPPER_APP_BUS_STRUCTURE_REFRESH_TIMELINE,
CLAPPER_APP_BUS_STRUCTURE_SIMPLE_SIGNAL,
CLAPPER_APP_BUS_STRUCTURE_DESC_WITH_DETAILS_SIGNAL,
CLAPPER_APP_BUS_STRUCTURE_ERROR_SIGNAL
};
static ClapperBusQuark _structure_quarks[] = {
{"unknown", 0},
{"prop-notify", 0},
{"refresh-streams", 0},
{"refresh-timeline", 0},
{"simple-signal", 0},
{"desc-with-details-signal", 0},
{"error-signal", 0},
{NULL, 0}
};
enum
{
CLAPPER_APP_BUS_FIELD_UNKNOWN = 0,
CLAPPER_APP_BUS_FIELD_PSPEC,
CLAPPER_APP_BUS_FIELD_SIGNAL_ID,
CLAPPER_APP_BUS_FIELD_DESC,
CLAPPER_APP_BUS_FIELD_DETAILS,
CLAPPER_APP_BUS_FIELD_ERROR,
CLAPPER_APP_BUS_FIELD_DEBUG_INFO
};
static ClapperBusQuark _field_quarks[] = {
{"unknown", 0},
{"pspec", 0},
{"signal-id", 0},
{"desc", 0},
{"details", 0},
{"error", 0},
{"debug-info", 0},
{NULL, 0}
};
#define _STRUCTURE_QUARK(q) (_structure_quarks[CLAPPER_APP_BUS_STRUCTURE_##q].quark)
#define _FIELD_QUARK(q) (_field_quarks[CLAPPER_APP_BUS_FIELD_##q].quark)
#define _MESSAGE_SRC_GOBJECT(msg) ((GObject *) GST_MESSAGE_SRC (msg))
void
clapper_app_bus_initialize (void)
{
gint i;
for (i = 0; _structure_quarks[i].name; ++i)
_structure_quarks[i].quark = g_quark_from_static_string (_structure_quarks[i].name);
for (i = 0; _field_quarks[i].name; ++i)
_field_quarks[i].quark = g_quark_from_static_string (_field_quarks[i].name);
}
void
clapper_app_bus_forward_message (ClapperAppBus *self, GstMessage *msg)
{
gst_bus_post (GST_BUS_CAST (self), gst_message_ref (msg));
}
/* FIXME: It should be faster to wait for gst_message_new_property_notify() from
* playbin bus and forward them to app bus instead of connecting to notify
* signals of playbin, so change into using gst_message_new_property_notify() here too */
void
clapper_app_bus_post_prop_notify (ClapperAppBus *self,
GstObject *src, GParamSpec *pspec)
{
GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (PROP_NOTIFY),
_FIELD_QUARK (PSPEC), G_TYPE_PARAM, pspec,
NULL);
gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure));
}
static inline void
_handle_prop_notify_msg (GstMessage *msg, const GstStructure *structure)
{
GParamSpec *pspec = NULL;
gst_structure_id_get (structure,
_FIELD_QUARK (PSPEC), G_TYPE_PARAM, &pspec,
NULL);
g_object_notify_by_pspec (_MESSAGE_SRC_GOBJECT (msg), pspec);
g_param_spec_unref (pspec);
}
void
clapper_app_bus_post_refresh_streams (ClapperAppBus *self, GstObject *src)
{
GstStructure *structure = gst_structure_new_id_empty (_STRUCTURE_QUARK (REFRESH_STREAMS));
gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure));
}
static inline void
_handle_refresh_streams_msg (GstMessage *msg, const GstStructure *structure)
{
ClapperPlayer *player = CLAPPER_PLAYER_CAST (GST_MESSAGE_SRC (msg));
clapper_player_refresh_streams (player);
}
void
clapper_app_bus_post_refresh_timeline (ClapperAppBus *self, GstObject *src)
{
GstStructure *structure = gst_structure_new_id_empty (_STRUCTURE_QUARK (REFRESH_TIMELINE));
gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure));
}
static inline void
_handle_refresh_timeline_msg (GstMessage *msg, const GstStructure *structure)
{
ClapperMediaItem *item = CLAPPER_MEDIA_ITEM_CAST (GST_MESSAGE_SRC (msg));
ClapperTimeline *timeline = clapper_media_item_get_timeline (item);
clapper_timeline_refresh (timeline);
}
void
clapper_app_bus_post_simple_signal (ClapperAppBus *self, GstObject *src, guint signal_id)
{
GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (SIMPLE_SIGNAL),
_FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, signal_id,
NULL);
gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure));
}
static inline void
_handle_simple_signal_msg (GstMessage *msg, const GstStructure *structure)
{
guint signal_id = 0;
gst_structure_id_get (structure,
_FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, &signal_id,
NULL);
g_signal_emit (_MESSAGE_SRC_GOBJECT (msg), signal_id, 0);
}
void
clapper_app_bus_post_desc_with_details_signal (ClapperAppBus *self,
GstObject *src, guint signal_id,
const gchar *desc, const gchar *details)
{
GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (DESC_WITH_DETAILS_SIGNAL),
_FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, signal_id,
_FIELD_QUARK (DESC), G_TYPE_STRING, desc,
_FIELD_QUARK (DETAILS), G_TYPE_STRING, details,
NULL);
gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure));
}
static inline void
_handle_desc_with_details_signal_msg (GstMessage *msg, const GstStructure *structure)
{
guint signal_id = 0;
gchar *desc = NULL, *details = NULL;
gst_structure_id_get (structure,
_FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, &signal_id,
_FIELD_QUARK (DESC), G_TYPE_STRING, &desc,
_FIELD_QUARK (DETAILS), G_TYPE_STRING, &details,
NULL);
g_signal_emit (_MESSAGE_SRC_GOBJECT (msg), signal_id, 0, desc, details);
g_free (desc);
g_free (details);
}
void
clapper_app_bus_post_error_signal (ClapperAppBus *self,
GstObject *src, guint signal_id,
GError *error, const gchar *debug_info)
{
GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (ERROR_SIGNAL),
_FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, signal_id,
_FIELD_QUARK (ERROR), G_TYPE_ERROR, error,
_FIELD_QUARK (DEBUG_INFO), G_TYPE_STRING, debug_info,
NULL);
gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure));
}
static inline void
_handle_error_signal_msg (GstMessage *msg, const GstStructure *structure)
{
guint signal_id = 0;
GError *error = NULL;
gchar *debug_info = NULL;
gst_structure_id_get (structure,
_FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, &signal_id,
_FIELD_QUARK (ERROR), G_TYPE_ERROR, &error,
_FIELD_QUARK (DEBUG_INFO), G_TYPE_STRING, &debug_info,
NULL);
g_signal_emit (_MESSAGE_SRC_GOBJECT (msg), signal_id, 0, error, debug_info);
g_clear_error (&error);
g_free (debug_info);
}
static gboolean
clapper_app_bus_message_func (GstBus *bus, GstMessage *msg, gpointer user_data G_GNUC_UNUSED)
{
if (G_LIKELY (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_APPLICATION)) {
const GstStructure *structure = gst_message_get_structure (msg);
GQuark quark = gst_structure_get_name_id (structure);
if (quark == _STRUCTURE_QUARK (PROP_NOTIFY))
_handle_prop_notify_msg (msg, structure);
else if (quark == _STRUCTURE_QUARK (REFRESH_STREAMS))
_handle_refresh_streams_msg (msg, structure);
else if (quark == _STRUCTURE_QUARK (REFRESH_TIMELINE))
_handle_refresh_timeline_msg (msg, structure);
else if (quark == _STRUCTURE_QUARK (SIMPLE_SIGNAL))
_handle_simple_signal_msg (msg, structure);
else if (quark == _STRUCTURE_QUARK (ERROR_SIGNAL))
_handle_error_signal_msg (msg, structure);
else if (quark == _STRUCTURE_QUARK (DESC_WITH_DETAILS_SIGNAL))
_handle_desc_with_details_signal_msg (msg, structure);
}
return G_SOURCE_CONTINUE;
}
/*
* clapper_app_bus_new:
*
* Returns: (transfer full): a new #ClapperAppBus instance
*/
ClapperAppBus *
clapper_app_bus_new (void)
{
GstBus *app_bus;
app_bus = GST_BUS_CAST (g_object_new (CLAPPER_TYPE_APP_BUS, NULL));
gst_object_ref_sink (app_bus);
gst_bus_add_watch (app_bus, (GstBusFunc) clapper_app_bus_message_func, NULL);
return CLAPPER_APP_BUS_CAST (app_bus);
}
static void
clapper_app_bus_init (ClapperAppBus *self)
{
}
static void
clapper_app_bus_finalize (GObject *object)
{
ClapperAppBus *self = CLAPPER_APP_BUS_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_app_bus_class_init (ClapperAppBusClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperappbus", 0,
"Clapper App Bus");
gobject_class->finalize = clapper_app_bus_finalize;
}

View File

@@ -0,0 +1,32 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gst/gst.h>
#include "clapper-audio-stream.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
ClapperStream * clapper_audio_stream_new (GstStream *gst_stream);
G_END_DECLS

View File

@@ -0,0 +1,429 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperAudioStream:
*
* Represents an audio stream within media.
*/
#include <gst/tag/tag.h>
#include "clapper-audio-stream-private.h"
#include "clapper-stream-private.h"
#define GST_CAT_DEFAULT clapper_audio_stream_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperAudioStream
{
ClapperStream parent;
gchar *codec;
guint bitrate;
gchar *sample_format;
gint sample_rate;
gint channels;
gchar *lang_code;
gchar *lang_name;
};
#define parent_class clapper_audio_stream_parent_class
G_DEFINE_TYPE (ClapperAudioStream, clapper_audio_stream, CLAPPER_TYPE_STREAM);
enum
{
PROP_0,
PROP_CODEC,
PROP_BITRATE,
PROP_SAMPLE_FORMAT,
PROP_SAMPLE_RATE,
PROP_CHANNELS,
PROP_LANG_CODE,
PROP_LANG_NAME,
PROP_LAST
};
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static void
_update_using_caps (ClapperAudioStream *self, GstCaps *caps)
{
ClapperStream *stream = CLAPPER_STREAM_CAST (self);
GstStructure *structure;
gint sample_rate = 0, channels = 0;
if (gst_caps_get_size (caps) == 0)
return;
structure = gst_caps_get_structure (caps, 0);
clapper_stream_set_string_prop (stream, param_specs[PROP_SAMPLE_FORMAT], &self->sample_format,
gst_structure_get_string (structure, "format"));
gst_structure_get_int (structure, "rate", &sample_rate);
clapper_stream_set_int_prop (stream, param_specs[PROP_SAMPLE_RATE], &self->sample_rate, sample_rate);
gst_structure_get_int (structure, "channels", &channels);
clapper_stream_set_int_prop (stream, param_specs[PROP_CHANNELS], &self->channels, channels);
}
static void
_update_using_tags (ClapperAudioStream *self, GstTagList *tags)
{
ClapperStream *stream = CLAPPER_STREAM_CAST (self);
gchar *codec = NULL, *lang_code = NULL, *lang_name = NULL;
guint bitrate = 0;
gst_tag_list_get_string_index (tags, GST_TAG_AUDIO_CODEC, 0, &codec);
clapper_stream_take_string_prop (stream, param_specs[PROP_CODEC], &self->codec, codec);
gst_tag_list_get_uint_index (tags, GST_TAG_BITRATE, 0, &bitrate);
clapper_stream_set_uint_prop (stream, param_specs[PROP_BITRATE], &self->bitrate, bitrate);
/* Prefer code (and name from it), fallback to extracted name */
if (!gst_tag_list_get_string_index (tags, GST_TAG_LANGUAGE_CODE, 0, &lang_code))
gst_tag_list_get_string_index (tags, GST_TAG_LANGUAGE_NAME, 0, &lang_name);
clapper_stream_take_string_prop (stream, param_specs[PROP_LANG_CODE], &self->lang_code, lang_code);
clapper_stream_take_string_prop (stream, param_specs[PROP_LANG_NAME], &self->lang_name, lang_name);
}
ClapperStream *
clapper_audio_stream_new (GstStream *gst_stream)
{
ClapperAudioStream *audio_stream;
audio_stream = g_object_new (CLAPPER_TYPE_AUDIO_STREAM,
"stream-type", CLAPPER_STREAM_TYPE_AUDIO, NULL);
gst_object_ref_sink (audio_stream);
clapper_stream_set_gst_stream (CLAPPER_STREAM_CAST (audio_stream), gst_stream);
return CLAPPER_STREAM_CAST (audio_stream);
}
/**
* clapper_audio_stream_get_codec:
* @stream: a #ClapperAudioStream
*
* Get codec used to encode @stream.
*
* Returns: (transfer full) (nullable): the audio codec of stream
* or %NULL if undetermined.
*/
gchar *
clapper_audio_stream_get_codec (ClapperAudioStream *self)
{
gchar *codec;
g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), NULL);
GST_OBJECT_LOCK (self);
codec = g_strdup (self->codec);
GST_OBJECT_UNLOCK (self);
return codec;
}
/**
* clapper_audio_stream_get_bitrate:
* @stream: a #ClapperAudioStream
*
* Get bitrate of audio @stream.
*
* Returns: the bitrate of audio stream.
*/
guint
clapper_audio_stream_get_bitrate (ClapperAudioStream *self)
{
guint bitrate;
g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), 0);
GST_OBJECT_LOCK (self);
bitrate = self->bitrate;
GST_OBJECT_UNLOCK (self);
return bitrate;
}
/**
* clapper_audio_stream_get_sample_format:
* @stream: a #ClapperAudioStream
*
* Get sample format of audio @stream.
*
* Returns: (transfer full) (nullable): the sample format of stream
* or %NULL if undetermined.
*/
gchar *
clapper_audio_stream_get_sample_format (ClapperAudioStream *self)
{
gchar *sample_format;
g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), NULL);
GST_OBJECT_LOCK (self);
sample_format = g_strdup (self->sample_format);
GST_OBJECT_UNLOCK (self);
return sample_format;
}
/**
* clapper_audio_stream_get_sample_rate:
* @stream: a #ClapperAudioStream
*
* Get sample rate of audio @stream (in Hz).
*
* Returns: the sample rate of audio stream.
*/
gint
clapper_audio_stream_get_sample_rate (ClapperAudioStream *self)
{
gint sample_rate;
g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), 0);
GST_OBJECT_LOCK (self);
sample_rate = self->sample_rate;
GST_OBJECT_UNLOCK (self);
return sample_rate;
}
/**
* clapper_audio_stream_get_channels:
* @stream: a #ClapperAudioStream
*
* Get number of audio channels in @stream.
*
* Returns: the number of audio channels.
*/
gint
clapper_audio_stream_get_channels (ClapperAudioStream *self)
{
gint channels;
g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), 0);
GST_OBJECT_LOCK (self);
channels = self->channels;
GST_OBJECT_UNLOCK (self);
return channels;
}
/**
* clapper_audio_stream_get_lang_code:
* @stream: a #ClapperAudioStream
*
* Get an ISO-639 language code of the @stream.
*
* Returns: (transfer full) (nullable): the language code of audio stream.
*/
gchar *
clapper_audio_stream_get_lang_code (ClapperAudioStream *self)
{
gchar *lang_code;
g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), NULL);
GST_OBJECT_LOCK (self);
lang_code = g_strdup (self->lang_code);
GST_OBJECT_UNLOCK (self);
return lang_code;
}
/**
* clapper_audio_stream_get_lang_name:
* @stream: a #ClapperAudioStream
*
* Get an ISO-639 language code of the @stream.
*
* Returns: (transfer full) (nullable): the language code of audio stream.
*/
gchar *
clapper_audio_stream_get_lang_name (ClapperAudioStream *self)
{
gchar *lang_name = NULL;
g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), NULL);
GST_OBJECT_LOCK (self);
/* Prefer from code as its translated to user locale,
* otherwise try to fallback to the one sent in tags */
if (self->lang_code)
lang_name = g_strdup (gst_tag_get_language_name (self->lang_code));
if (!lang_name)
lang_name = g_strdup (self->lang_name);
GST_OBJECT_UNLOCK (self);
return lang_name;
}
static void
clapper_audio_stream_init (ClapperAudioStream *self)
{
}
static void
clapper_audio_stream_finalize (GObject *object)
{
ClapperAudioStream *self = CLAPPER_AUDIO_STREAM_CAST (object);
g_free (self->codec);
g_free (self->sample_format);
g_free (self->lang_code);
g_free (self->lang_name);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_audio_stream_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperAudioStream *self = CLAPPER_AUDIO_STREAM_CAST (object);
switch (prop_id) {
case PROP_CODEC:
g_value_take_string (value, clapper_audio_stream_get_codec (self));
break;
case PROP_BITRATE:
g_value_set_uint (value, clapper_audio_stream_get_bitrate (self));
break;
case PROP_SAMPLE_FORMAT:
g_value_take_string (value, clapper_audio_stream_get_sample_format (self));
break;
case PROP_SAMPLE_RATE:
g_value_set_int (value, clapper_audio_stream_get_sample_rate (self));
break;
case PROP_CHANNELS:
g_value_set_int (value, clapper_audio_stream_get_channels (self));
break;
case PROP_LANG_CODE:
g_value_take_string (value, clapper_audio_stream_get_lang_code (self));
break;
case PROP_LANG_NAME:
g_value_take_string (value, clapper_audio_stream_get_lang_name (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_audio_stream_internal_stream_updated (ClapperStream *stream,
GstCaps *caps, GstTagList *tags)
{
ClapperAudioStream *self = CLAPPER_AUDIO_STREAM_CAST (stream);
CLAPPER_STREAM_CLASS (parent_class)->internal_stream_updated (stream, caps, tags);
if (caps)
_update_using_caps (self, caps);
if (tags)
_update_using_tags (self, tags);
}
static void
clapper_audio_stream_class_init (ClapperAudioStreamClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
ClapperStreamClass *stream_class = (ClapperStreamClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperaudiostream", 0,
"Clapper Audio Stream");
gobject_class->get_property = clapper_audio_stream_get_property;
gobject_class->finalize = clapper_audio_stream_finalize;
stream_class->internal_stream_updated = clapper_audio_stream_internal_stream_updated;
/**
* ClapperAudioStream:codec:
*
* Stream codec.
*/
param_specs[PROP_CODEC] = g_param_spec_string ("codec",
NULL, NULL, NULL,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperAudioStream:bitrate:
*
* Stream bitrate.
*/
param_specs[PROP_BITRATE] = g_param_spec_uint ("bitrate",
NULL, NULL, 0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperAudioStream:sample-format:
*
* Stream sample format.
*/
param_specs[PROP_SAMPLE_FORMAT] = g_param_spec_string ("sample-format",
NULL, NULL, NULL,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperAudioStream:sample-rate:
*
* Stream sample rate (in Hz).
*/
param_specs[PROP_SAMPLE_RATE] = g_param_spec_int ("sample-rate",
NULL, NULL, 0, G_MAXINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperAudioStream:channels:
*
* Stream number of audio channels.
*/
param_specs[PROP_CHANNELS] = g_param_spec_int ("channels",
NULL, NULL, 0, G_MAXINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperAudioStream:lang-code:
*
* Stream language code in ISO-639 format.
*/
param_specs[PROP_LANG_CODE] = g_param_spec_string ("lang-code",
NULL, NULL, NULL,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperAudioStream:lang-name:
*
* Stream full language name determined from lang code.
*/
param_specs[PROP_LANG_NAME] = g_param_spec_string ("lang-name",
NULL, NULL, NULL,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
}

View File

@@ -0,0 +1,51 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <clapper/clapper-stream.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_AUDIO_STREAM (clapper_audio_stream_get_type())
#define CLAPPER_AUDIO_STREAM_CAST(obj) ((ClapperAudioStream *)(obj))
G_DECLARE_FINAL_TYPE (ClapperAudioStream, clapper_audio_stream, CLAPPER, AUDIO_STREAM, ClapperStream)
gchar * clapper_audio_stream_get_codec (ClapperAudioStream *stream);
guint clapper_audio_stream_get_bitrate (ClapperAudioStream *stream);
gchar * clapper_audio_stream_get_sample_format (ClapperAudioStream *stream);
gint clapper_audio_stream_get_sample_rate (ClapperAudioStream *stream);
gint clapper_audio_stream_get_channels (ClapperAudioStream *stream);
gchar * clapper_audio_stream_get_lang_code (ClapperAudioStream *stream);
gchar * clapper_audio_stream_get_lang_name (ClapperAudioStream *stream);
G_END_DECLS

View File

@@ -0,0 +1,32 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
G_BEGIN_DECLS
typedef struct
{
const gchar *name;
GQuark quark;
} ClapperBusQuark;
G_END_DECLS

View File

@@ -0,0 +1,69 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
G_BEGIN_DECLS
typedef enum
{
CLAPPER_PLAYER_PLAY_FLAG_VIDEO = (1 << 0),
CLAPPER_PLAYER_PLAY_FLAG_AUDIO = (1 << 1),
CLAPPER_PLAYER_PLAY_FLAG_TEXT = (1 << 2),
CLAPPER_PLAYER_PLAY_FLAG_VIS = (1 << 3),
CLAPPER_PLAYER_PLAY_FLAG_SOFT_VOLUME = (1 << 4),
CLAPPER_PLAYER_PLAY_FLAG_NATIVE_AUDIO = (1 << 5),
CLAPPER_PLAYER_PLAY_FLAG_NATIVE_VIDEO = (1 << 6),
CLAPPER_PLAYER_PLAY_FLAG_DOWNLOAD = (1 << 7),
CLAPPER_PLAYER_PLAY_FLAG_BUFFERING = (1 << 8),
CLAPPER_PLAYER_PLAY_FLAG_DEINTERLACE = (1 << 9),
CLAPPER_PLAYER_PLAY_FLAG_SOFT_COLORBALANCE = (1 << 10),
CLAPPER_PLAYER_PLAY_FLAG_FORCE_FILTERS = (1 << 11),
CLAPPER_PLAYER_PLAY_FLAG_FORCE_SW_DECODERS = (1 << 12)
} ClapperPlayerPlayFlags;
typedef enum
{
CLAPPER_FEATURES_MANAGER_EVENT_UNKNOWN = 0,
CLAPPER_FEATURES_MANAGER_EVENT_FEATURE_ADDED,
CLAPPER_FEATURES_MANAGER_EVENT_FEATURE_PROPERTY_CHANGED,
CLAPPER_FEATURES_MANAGER_EVENT_STATE_CHANGED,
CLAPPER_FEATURES_MANAGER_EVENT_POSITION_CHANGED,
CLAPPER_FEATURES_MANAGER_EVENT_SPEED_CHANGED,
CLAPPER_FEATURES_MANAGER_EVENT_VOLUME_CHANGED,
CLAPPER_FEATURES_MANAGER_EVENT_MUTE_CHANGED,
CLAPPER_FEATURES_MANAGER_EVENT_PLAYED_ITEM_CHANGED,
CLAPPER_FEATURES_MANAGER_EVENT_ITEM_UPDATED,
CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_ADDED,
CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_REMOVED,
CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_REPOSITIONED,
CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_CLEARED,
CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_PROGRESSION_CHANGED
} ClapperFeaturesManagerEvent;
typedef enum
{
CLAPPER_QUEUE_ITEM_CHANGE_NORMAL = 1,
CLAPPER_QUEUE_ITEM_CHANGE_INSTANT = 2,
CLAPPER_QUEUE_ITEM_CHANGE_GAPLESS = 3,
} ClapperQueueItemChangeMode;
G_END_DECLS

View File

@@ -0,0 +1,130 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <clapper/clapper-enum-types.h>
G_BEGIN_DECLS
/**
* ClapperPlayerState:
* @CLAPPER_PLAYER_STATE_STOPPED: Player is stopped.
* @CLAPPER_PLAYER_STATE_BUFFERING: Player is buffering.
* @CLAPPER_PLAYER_STATE_PAUSED: Player is paused.
* @CLAPPER_PLAYER_STATE_PLAYING: Player is playing.
*/
typedef enum
{
CLAPPER_PLAYER_STATE_STOPPED = 0,
CLAPPER_PLAYER_STATE_BUFFERING,
CLAPPER_PLAYER_STATE_PAUSED,
CLAPPER_PLAYER_STATE_PLAYING,
} ClapperPlayerState;
/**
* ClapperPlayerSeekMethod:
* @CLAPPER_PLAYER_SEEK_METHOD_ACCURATE: Seek to exact position (slow).
* @CLAPPER_PLAYER_SEEK_METHOD_NORMAL: Seek to approximated position.
* @CLAPPER_PLAYER_SEEK_METHOD_FAST: Seek to position of nearest keyframe (fast).
*/
typedef enum
{
CLAPPER_PLAYER_SEEK_METHOD_ACCURATE = 0,
CLAPPER_PLAYER_SEEK_METHOD_NORMAL,
CLAPPER_PLAYER_SEEK_METHOD_FAST,
} ClapperPlayerSeekMethod;
/**
* ClapperQueueProgressionMode:
* @CLAPPER_QUEUE_PROGRESSION_NONE: Queue will not change current item after playback finishes.
* @CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE: Queue selects items one after another until the end.
* When end of queue is reached, this mode will continue one another item is added to the queue,
* playing it if player autoplay property is set, otherwise current player state is kept.
* @CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM: Queue keeps repeating current media item.
* @CLAPPER_QUEUE_PROGRESSION_CAROUSEL: Queue starts from beginning after last media item.
* @CLAPPER_QUEUE_PROGRESSION_SHUFFLE: Queue selects a random media item after current one.
* Shuffle mode will avoid reselecting previously shuffled items as long as possible.
* After it runs out of unused items, shuffling begins anew.
*/
typedef enum
{
CLAPPER_QUEUE_PROGRESSION_NONE = 0,
CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE,
CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM,
CLAPPER_QUEUE_PROGRESSION_CAROUSEL,
CLAPPER_QUEUE_PROGRESSION_SHUFFLE,
} ClapperQueueProgressionMode;
/**
* ClapperMarkerType:
* @CLAPPER_MARKER_TYPE_UNKNOWN: Unknown marker type.
* @CLAPPER_MARKER_TYPE_TITLE: A title marker in timeline.
* @CLAPPER_MARKER_TYPE_CHAPTER: A chapter marker in timeline.
* @CLAPPER_MARKER_TYPE_TRACK: A track marker in timeline.
* @CLAPPER_STREAM_TYPE_CUSTOM_1: A custom marker 1 for free usage by application.
* @CLAPPER_STREAM_TYPE_CUSTOM_2: A custom marker 2 for free usage by application.
* @CLAPPER_STREAM_TYPE_CUSTOM_3: A custom marker 3 for free usage by application.
*/
typedef enum
{
CLAPPER_MARKER_TYPE_UNKNOWN = 0,
CLAPPER_MARKER_TYPE_TITLE,
CLAPPER_MARKER_TYPE_CHAPTER,
CLAPPER_MARKER_TYPE_TRACK,
CLAPPER_MARKER_TYPE_CUSTOM_1 = 101,
CLAPPER_MARKER_TYPE_CUSTOM_2 = 102,
CLAPPER_MARKER_TYPE_CUSTOM_3 = 103,
} ClapperMarkerType;
/**
* ClapperStreamType:
* @CLAPPER_STREAM_TYPE_UNKNOWN: Unknown stream type.
* @CLAPPER_STREAM_TYPE_VIDEO: Stream is a #ClapperVideoStream.
* @CLAPPER_STREAM_TYPE_AUDIO: Stream is a #ClapperAudioStream.
* @CLAPPER_STREAM_TYPE_SUBTITLE: Stream is a #ClapperSubtitleStream.
*/
typedef enum
{
CLAPPER_STREAM_TYPE_UNKNOWN = 0,
CLAPPER_STREAM_TYPE_VIDEO,
CLAPPER_STREAM_TYPE_AUDIO,
CLAPPER_STREAM_TYPE_SUBTITLE,
} ClapperStreamType;
/**
* ClapperDiscovererDiscoveryMode:
* @CLAPPER_DISCOVERER_DISCOVERY_ALWAYS: Run discovery for every single media item added to [class@Clapper.Queue].
* This mode is useful when application presents a list of items to select from to the user before playback.
* It will scan every single item in queue, so user can have an updated list of items when selecting what to play.
* @CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT: Only run discovery on an item if it is not a currently selected item in [class@Clapper.Queue].
* This mode is optimal when application always plays (or at least goes into paused) after selecting item from queue.
* It will skip discovery of such items since they will be discovered by [class@Clapper.Player] anyway.
*/
typedef enum
{
CLAPPER_DISCOVERER_DISCOVERY_ALWAYS = 0,
CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT,
} ClapperDiscovererDiscoveryMode;
G_END_DECLS

View File

@@ -0,0 +1,72 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <clapper/clapper-feature.h>
#include <clapper/clapper-enums.h>
G_BEGIN_DECLS
G_GNUC_INTERNAL
void clapper_feature_call_prepare (ClapperFeature *feature);
G_GNUC_INTERNAL
void clapper_feature_call_unprepare (ClapperFeature *feature);
G_GNUC_INTERNAL
void clapper_feature_call_property_changed (ClapperFeature *feature, GParamSpec *pspec);
G_GNUC_INTERNAL
void clapper_feature_call_state_changed (ClapperFeature *feature, ClapperPlayerState state);
G_GNUC_INTERNAL
void clapper_feature_call_position_changed (ClapperFeature *feature, gdouble position);
G_GNUC_INTERNAL
void clapper_feature_call_speed_changed (ClapperFeature *feature, gdouble speed);
G_GNUC_INTERNAL
void clapper_feature_call_volume_changed (ClapperFeature *feature, gdouble volume);
G_GNUC_INTERNAL
void clapper_feature_call_mute_changed (ClapperFeature *feature, gboolean mute);
G_GNUC_INTERNAL
void clapper_feature_call_played_item_changed (ClapperFeature *feature, ClapperMediaItem *item);
G_GNUC_INTERNAL
void clapper_feature_call_item_updated (ClapperFeature *feature, ClapperMediaItem *item);
G_GNUC_INTERNAL
void clapper_feature_call_queue_item_added (ClapperFeature *feature, ClapperMediaItem *item, guint index);
G_GNUC_INTERNAL
void clapper_feature_call_queue_item_removed (ClapperFeature *feature, ClapperMediaItem *item, guint index);
G_GNUC_INTERNAL
void clapper_feature_call_queue_item_repositioned (ClapperFeature *self, guint before, guint after);
G_GNUC_INTERNAL
void clapper_feature_call_queue_cleared (ClapperFeature *feature);
G_GNUC_INTERNAL
void clapper_feature_call_queue_progression_changed (ClapperFeature *feature, ClapperQueueProgressionMode mode);
G_END_DECLS

View File

@@ -0,0 +1,225 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperFeature:
*
* A base class for creating new features for the player.
*
* Feature objects are meant for adding additional functionalities that
* are supposed to either act on playback/properties changes and/or change
* them themselves due to some external signal/event.
*
* For reacting to playback changes subclass should override this class
* virtual functions logic, while for controlling playback implementation
* may call [method@Gst.Object.get_parent] to acquire a weak reference on
* a parent [class@Clapper.Player] object feature was added to.
*/
#include "clapper-feature.h"
#include "clapper-feature-private.h"
#include "clapper-player-private.h"
#define GST_CAT_DEFAULT clapper_feature_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
typedef struct _ClapperFeaturePrivate ClapperFeaturePrivate;
struct _ClapperFeaturePrivate
{
gboolean prepared;
};
#define parent_class clapper_feature_parent_class
G_DEFINE_TYPE_WITH_PRIVATE (ClapperFeature, clapper_feature, GST_TYPE_OBJECT);
#define CALL_WITH_ARGS(_feature,_vfunc,...) \
ClapperFeaturePrivate *priv = clapper_feature_get_instance_private (_feature); \
if (priv->prepared) { \
ClapperFeatureClass *feature_class = CLAPPER_FEATURE_GET_CLASS (_feature); \
if (feature_class->_vfunc) \
feature_class->_vfunc (_feature, __VA_ARGS__); }
#define CALL_WITHOUT_ARGS(_feature,_vfunc) \
ClapperFeaturePrivate *priv = clapper_feature_get_instance_private (_feature); \
if (priv->prepared) { \
ClapperFeatureClass *feature_class = CLAPPER_FEATURE_GET_CLASS (_feature); \
if (feature_class->_vfunc) \
feature_class->_vfunc (_feature); }
void
clapper_feature_call_prepare (ClapperFeature *self)
{
ClapperFeaturePrivate *priv = clapper_feature_get_instance_private (self);
if (!priv->prepared) {
ClapperFeatureClass *feature_class = CLAPPER_FEATURE_GET_CLASS (self);
gboolean prepared = TRUE; // mark subclass without prepare method as prepared
if (feature_class->prepare)
prepared = feature_class->prepare (self);
priv->prepared = prepared;
}
}
void
clapper_feature_call_unprepare (ClapperFeature *self)
{
ClapperFeaturePrivate *priv = clapper_feature_get_instance_private (self);
if (priv->prepared) {
ClapperFeatureClass *feature_class = CLAPPER_FEATURE_GET_CLASS (self);
gboolean unprepared = TRUE; // mark subclass without unprepare method as unprepared
if (feature_class->unprepare)
unprepared = feature_class->unprepare (self);
priv->prepared = !unprepared;
}
}
void
clapper_feature_call_property_changed (ClapperFeature *self, GParamSpec *pspec)
{
CALL_WITH_ARGS (self, property_changed, pspec);
}
void
clapper_feature_call_state_changed (ClapperFeature *self, ClapperPlayerState state)
{
CALL_WITH_ARGS (self, state_changed, state);
}
void
clapper_feature_call_position_changed (ClapperFeature *self, gdouble position)
{
CALL_WITH_ARGS (self, position_changed, position);
}
void
clapper_feature_call_speed_changed (ClapperFeature *self, gdouble speed)
{
CALL_WITH_ARGS (self, speed_changed, speed);
}
void
clapper_feature_call_volume_changed (ClapperFeature *self, gdouble volume)
{
CALL_WITH_ARGS (self, volume_changed, volume);
}
void
clapper_feature_call_mute_changed (ClapperFeature *self, gboolean mute)
{
CALL_WITH_ARGS (self, mute_changed, mute);
}
void
clapper_feature_call_played_item_changed (ClapperFeature *self, ClapperMediaItem *item)
{
CALL_WITH_ARGS (self, played_item_changed, item);
}
void
clapper_feature_call_item_updated (ClapperFeature *self, ClapperMediaItem *item)
{
CALL_WITH_ARGS (self, item_updated, item);
}
void
clapper_feature_call_queue_item_added (ClapperFeature *self, ClapperMediaItem *item, guint index)
{
CALL_WITH_ARGS (self, queue_item_added, item, index);
}
void
clapper_feature_call_queue_item_removed (ClapperFeature *self, ClapperMediaItem *item, guint index)
{
CALL_WITH_ARGS (self, queue_item_removed, item, index);
}
void
clapper_feature_call_queue_item_repositioned (ClapperFeature *self, guint before, guint after)
{
CALL_WITH_ARGS (self, queue_item_repositioned, before, after);
}
void
clapper_feature_call_queue_cleared (ClapperFeature *self)
{
CALL_WITHOUT_ARGS (self, queue_cleared);
}
void
clapper_feature_call_queue_progression_changed (ClapperFeature *self, ClapperQueueProgressionMode mode)
{
CALL_WITH_ARGS (self, queue_progression_changed, mode);
}
static void
clapper_feature_init (ClapperFeature *self)
{
}
static void
clapper_feature_dispatch_properties_changed (GObject *object,
guint n_pspecs, GParamSpec **pspecs)
{
ClapperPlayer *player;
if ((player = CLAPPER_PLAYER_CAST (gst_object_get_parent (GST_OBJECT_CAST (object))))) {
ClapperFeaturesManager *features_manager;
if ((features_manager = clapper_player_get_features_manager (player))) {
guint i;
for (i = 0; i < n_pspecs; ++i) {
clapper_features_manager_trigger_property_changed (features_manager,
CLAPPER_FEATURE_CAST (object), pspecs[i]);
}
}
gst_object_unref (player);
}
G_OBJECT_CLASS (parent_class)->dispatch_properties_changed (object, n_pspecs, pspecs);
}
static void
clapper_feature_finalize (GObject *object)
{
ClapperFeature *self = CLAPPER_FEATURE_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_feature_class_init (ClapperFeatureClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperfeature", 0,
"Clapper Feature");
gobject_class->dispatch_properties_changed = clapper_feature_dispatch_properties_changed;
gobject_class->finalize = clapper_feature_finalize;
}

View File

@@ -0,0 +1,219 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
#include <gst/gst.h>
#include <clapper/clapper-media-item.h>
#include <clapper/clapper-enums.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_FEATURE (clapper_feature_get_type())
#define CLAPPER_FEATURE_CAST(obj) ((ClapperFeature *)(obj))
G_DECLARE_DERIVABLE_TYPE (ClapperFeature, clapper_feature, CLAPPER, FEATURE, GstObject)
/**
* ClapperFeatureClass:
* @parent_class: The object class structure.
* @prepare: Prepare feature for operation (optional).
* @unprepare: Revert the changes done in @prepare (optional).
* @property_changed: A property of @feature changed its value.
* @state_changed: Player state was changed.
* @position_changed: Player position was changed.
* @speed_changed: Player speed was changed.
* @volume_changed: Player volume was changed.
* @mute_changed: Player mute state was changed.
* @played_item_changed: Currently playing media item got changed.
* @item_updated: An item in queue got updated.
* @queue_item_added: An item was added to the queue.
* @queue_item_removed: An item was removed from queue.
* @queue_item_reposition: An item changed position within queue.
* @queue_cleared: All items were removed from queue.
* @queue_progression_changed: Progression mode of the queue was changed.
*/
struct _ClapperFeatureClass
{
GstObjectClass parent_class;
/**
* ClapperFeatureClass::prepare:
* @feature: a #ClapperFeature
*
* Prepare feature for operation (optional).
*
* This is different from init() as its called from features thread once
* feature is added to the player, so it can already access it parent using
* gst_object_get_parent(). If it fails, no other method will be called.
*
* Returns: %TRUE on success, %FALSE otherwise.
*/
gboolean (* prepare) (ClapperFeature *feature);
/**
* ClapperFeatureClass::unprepare:
* @feature: a #ClapperFeature
*
* Revert the changes done in @prepare (optional).
*
* Returns: %TRUE on success, %FALSE otherwise.
*/
gboolean (* unprepare) (ClapperFeature *feature);
/**
* ClapperFeatureClass::property_changed:
* @feature: a #ClapperFeature
* @pspec: a #GParamSpec
*
* A property of @feature changed its value.
*
* Useful for reconfiguring @feature, since unlike "notify" signal
* this is always called from the thread that feature works on and
* only after feature was prepared.
*/
void (* property_changed) (ClapperFeature *feature, GParamSpec *pspec);
/**
* ClapperFeatureClass::state_changed:
* @feature: a #ClapperFeature
* @state: a #ClapperPlayerState
*
* Player state was changed.
*/
void (* state_changed) (ClapperFeature *feature, ClapperPlayerState state);
/**
* ClapperFeatureClass::position_changed:
* @feature: a #ClapperFeature
* @position: a decimal number with current position in seconds
*
* Player position was changed.
*/
void (* position_changed) (ClapperFeature *feature, gdouble position);
/**
* ClapperFeatureClass::speed_changed:
* @feature: a #ClapperFeature
* @speed: the playback speed multiplier
*
* Player speed was changed.
*/
void (* speed_changed) (ClapperFeature *feature, gdouble speed);
/**
* ClapperFeatureClass::volume_changed:
* @feature: a #ClapperFeature
* @volume: the volume level
*
* Player volume was changed.
*/
void (* volume_changed) (ClapperFeature *feature, gdouble volume);
/**
* ClapperFeatureClass::mute_changed:
* @feature: a #ClapperFeature
* @mute: %TRUE if player is muted, %FALSE otherwise
*
* Player mute state was changed.
*/
void (* mute_changed) (ClapperFeature *feature, gboolean mute);
/**
* ClapperFeatureClass::played_item_changed:
* @feature: a #ClapperFeature
* @item: a #ClapperMediaItem that is now playing
*
* New media item started playing. All following events (such as position changes)
* will be related to this @item from now on.
*/
void (* played_item_changed) (ClapperFeature *feature, ClapperMediaItem *item);
/**
* ClapperFeatureClass::item_updated:
* @feature: a #ClapperFeature
* @item: a #ClapperMediaItem that was updated
*
* An item in queue got updated. This might be (or not) currently
* played item. Implementations can get parent player object
* if they want to check that from its queue.
*/
void (* item_updated) (ClapperFeature *feature, ClapperMediaItem *item);
/**
* ClapperFeatureClass::queue_item_added:
* @feature: a #ClapperFeature
* @item: a #ClapperMediaItem that was added
* @index: position at which @item was placed in queue
*
* An item was added to the queue.
*/
void (* queue_item_added) (ClapperFeature *feature, ClapperMediaItem *item, guint index);
/**
* ClapperFeatureClass::queue_item_removed:
* @feature: a #ClapperFeature
* @item: a #ClapperMediaItem that was removed
* @index: position from which @item was removed in queue
*
* An item was removed from queue.
*/
void (* queue_item_removed) (ClapperFeature *feature, ClapperMediaItem *item, guint index);
/**
* ClapperFeatureClass::queue_item_reposition:
* @feature: a #ClapperFeature
* @before: position from which #ClapperMediaItem was removed
* @after: position at which #ClapperMediaItem was inserted after removal
*
* An item changed position within queue.
*/
void (* queue_item_repositioned) (ClapperFeature *feature, guint before, guint after);
/**
* ClapperFeatureClass::queue_cleared:
* @feature: a #ClapperFeature
*
* All items were removed from queue. Note that in such event
* @queue_item_removed will NOT be called for each item for performance reasons.
* You probably want to implement this function if you also implemented item removal.
*/
void (* queue_cleared) (ClapperFeature *feature);
/**
* ClapperFeatureClass::queue_progression_changed:
* @feature: a #ClapperFeature
* @mode: a #ClapperQueueProgressionMode
*
* Progression mode of the queue was changed.
*/
void (* queue_progression_changed) (ClapperFeature *feature, ClapperQueueProgressionMode mode);
/*< private >*/
gpointer padding[8];
};
G_END_DECLS

View File

@@ -0,0 +1,44 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <glib-object.h>
#include "clapper-features-manager-private.h"
#include "clapper-enums-private.h"
G_BEGIN_DECLS
#define CLAPPER_TYPE_FEATURES_BUS (clapper_features_bus_get_type())
#define CLAPPER_FEATURES_BUS_CAST(obj) ((ClapperFeaturesBus *)(obj))
/**
* ClapperFeaturesBus:
*/
G_DECLARE_FINAL_TYPE (ClapperFeaturesBus, clapper_features_bus, CLAPPER, FEATURES_BUS, GstBus)
void clapper_features_bus_initialize (void);
ClapperFeaturesBus * clapper_features_bus_new (void);
void clapper_features_bus_post_event (ClapperFeaturesBus *features_bus, ClapperFeaturesManager *src, ClapperFeaturesManagerEvent event, GValue *value, GValue *extra_value);
G_END_DECLS

View File

@@ -0,0 +1,168 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "clapper-bus-private.h"
#include "clapper-features-manager-private.h"
#include "clapper-features-bus-private.h"
#define GST_CAT_DEFAULT clapper_features_bus_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperFeaturesBus
{
GstBus parent;
};
#define parent_class clapper_features_bus_parent_class
G_DEFINE_TYPE (ClapperFeaturesBus, clapper_features_bus, GST_TYPE_BUS);
enum
{
CLAPPER_FEATURES_BUS_STRUCTURE_UNKNOWN = 0,
CLAPPER_FEATURES_BUS_STRUCTURE_EVENT
};
static ClapperBusQuark _structure_quarks[] = {
{"unknown", 0},
{"event", 0},
{NULL, 0}
};
enum
{
CLAPPER_FEATURES_BUS_FIELD_UNKNOWN = 0,
CLAPPER_FEATURES_BUS_FIELD_EVENT,
CLAPPER_FEATURES_BUS_FIELD_VALUE,
CLAPPER_FEATURES_BUS_FIELD_EXTRA_VALUE
};
static ClapperBusQuark _field_quarks[] = {
{"unknown", 0},
{"event", 0},
{"value", 0},
{"extra-value", 0},
{NULL, 0}
};
#define _STRUCTURE_QUARK(q) (_structure_quarks[CLAPPER_FEATURES_BUS_STRUCTURE_##q].quark)
#define _FIELD_QUARK(q) (_field_quarks[CLAPPER_FEATURES_BUS_FIELD_##q].quark)
#define _MESSAGE_SRC_CLAPPER_FEATURES_MANAGER(msg) ((ClapperFeaturesManager *) GST_MESSAGE_SRC (msg))
void
clapper_features_bus_initialize (void)
{
gint i;
for (i = 0; _structure_quarks[i].name; ++i)
_structure_quarks[i].quark = g_quark_from_static_string (_structure_quarks[i].name);
for (i = 0; _field_quarks[i].name; ++i)
_field_quarks[i].quark = g_quark_from_static_string (_field_quarks[i].name);
}
void
clapper_features_bus_post_event (ClapperFeaturesBus *self,
ClapperFeaturesManager *src, ClapperFeaturesManagerEvent event,
GValue *value, GValue *extra_value)
{
GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (EVENT),
_FIELD_QUARK (EVENT), G_TYPE_ENUM, event,
NULL);
if (value)
gst_structure_id_take_value (structure, _FIELD_QUARK (VALUE), value);
if (extra_value)
gst_structure_id_take_value (structure, _FIELD_QUARK (EXTRA_VALUE), extra_value);
gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (
GST_OBJECT_CAST (src), structure));
}
static inline void
_handle_event_msg (GstMessage *msg, const GstStructure *structure,
ClapperFeaturesManager *features_manager)
{
ClapperFeaturesManagerEvent event = CLAPPER_FEATURES_MANAGER_EVENT_UNKNOWN;
const GValue *value = gst_structure_id_get_value (structure, _FIELD_QUARK (VALUE));
const GValue *extra_value = gst_structure_id_get_value (structure, _FIELD_QUARK (EXTRA_VALUE));
gst_structure_id_get (structure,
_FIELD_QUARK (EVENT), G_TYPE_ENUM, &event,
NULL);
clapper_features_manager_handle_event (features_manager, event, value, extra_value);
}
static gboolean
clapper_features_bus_message_func (GstBus *bus, GstMessage *msg, gpointer user_data G_GNUC_UNUSED)
{
if (G_LIKELY (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_APPLICATION)) {
ClapperFeaturesManager *features_manager = _MESSAGE_SRC_CLAPPER_FEATURES_MANAGER (msg);
const GstStructure *structure = gst_message_get_structure (msg);
GQuark quark = gst_structure_get_name_id (structure);
if (quark == _STRUCTURE_QUARK (EVENT))
_handle_event_msg (msg, structure, features_manager);
}
return G_SOURCE_CONTINUE;
}
/*
* clapper_features_bus_new:
*
* Returns: (transfer full): a new #ClapperFeaturesBus instance.
*/
ClapperFeaturesBus *
clapper_features_bus_new (void)
{
GstBus *features_bus;
features_bus = GST_BUS_CAST (g_object_new (CLAPPER_TYPE_FEATURES_BUS, NULL));
gst_object_ref_sink (features_bus);
gst_bus_add_watch (features_bus, (GstBusFunc) clapper_features_bus_message_func, NULL);
return CLAPPER_FEATURES_BUS_CAST (features_bus);
}
static void
clapper_features_bus_init (ClapperFeaturesBus *self)
{
}
static void
clapper_features_bus_finalize (GObject *object)
{
ClapperFeaturesBus *self = CLAPPER_FEATURES_BUS_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_features_bus_class_init (ClapperFeaturesBusClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperfeaturesbus", 0,
"Clapper Features Bus");
gobject_class->finalize = clapper_features_bus_finalize;
}

View File

@@ -0,0 +1,81 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include "clapper-enums-private.h"
#include "clapper-threaded-object.h"
#include "clapper-feature.h"
G_BEGIN_DECLS
#define CLAPPER_TYPE_FEATURES_MANAGER (clapper_features_manager_get_type())
#define CLAPPER_FEATURES_MANAGER_CAST(obj) ((ClapperFeaturesManager *)(obj))
G_DECLARE_FINAL_TYPE (ClapperFeaturesManager, clapper_features_manager, CLAPPER, FEATURES_MANAGER, ClapperThreadedObject)
G_GNUC_INTERNAL
ClapperFeaturesManager * clapper_features_manager_new (void);
G_GNUC_INTERNAL
void clapper_features_manager_add_feature (ClapperFeaturesManager *features, ClapperFeature *feature, GstObject *parent);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_property_changed (ClapperFeaturesManager *self, ClapperFeature *feature, GParamSpec *pspec);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_state_changed (ClapperFeaturesManager *features, ClapperPlayerState state);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_position_changed (ClapperFeaturesManager *features, gdouble position);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_speed_changed (ClapperFeaturesManager *features, gdouble speed);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_volume_changed (ClapperFeaturesManager *features, gdouble volume);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_mute_changed (ClapperFeaturesManager *features, gboolean mute);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_played_item_changed (ClapperFeaturesManager *features, ClapperMediaItem *item);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_item_updated (ClapperFeaturesManager *features, ClapperMediaItem *item);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_queue_item_added (ClapperFeaturesManager *features, ClapperMediaItem *item, guint index);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_queue_item_removed (ClapperFeaturesManager *features, ClapperMediaItem *item, guint index);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_queue_item_repositioned (ClapperFeaturesManager *features, guint before, guint after);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_queue_cleared (ClapperFeaturesManager *features);
G_GNUC_INTERNAL
void clapper_features_manager_trigger_queue_progression_changed (ClapperFeaturesManager *features, ClapperQueueProgressionMode mode);
G_GNUC_INTERNAL
void clapper_features_manager_handle_event (ClapperFeaturesManager *features, ClapperFeaturesManagerEvent event, const GValue *value, const GValue *extra_value);
G_END_DECLS

View File

@@ -0,0 +1,371 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "clapper-features-manager-private.h"
#include "clapper-features-bus-private.h"
#include "clapper-feature-private.h"
#define GST_CAT_DEFAULT clapper_features_manager_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperFeaturesManager
{
ClapperThreadedObject parent;
GPtrArray *features;
ClapperFeaturesBus *bus;
};
#define parent_class clapper_features_manager_parent_class
G_DEFINE_TYPE (ClapperFeaturesManager, clapper_features_manager, CLAPPER_TYPE_THREADED_OBJECT);
static inline void
_post_object (ClapperFeaturesManager *self, ClapperFeaturesManagerEvent event, GObject *data)
{
GValue value = G_VALUE_INIT;
g_value_init (&value, G_TYPE_OBJECT);
g_value_set_object (&value, data);
clapper_features_bus_post_event (self->bus, self, event, &value, NULL);
}
static inline void
_post_int (ClapperFeaturesManager *self, ClapperFeaturesManagerEvent event, gint data)
{
GValue value = G_VALUE_INIT;
g_value_init (&value, G_TYPE_INT);
g_value_set_int (&value, data);
clapper_features_bus_post_event (self->bus, self, event, &value, NULL);
}
static inline void
_post_double (ClapperFeaturesManager *self, ClapperFeaturesManagerEvent event, gdouble data)
{
GValue value = G_VALUE_INIT;
g_value_init (&value, G_TYPE_DOUBLE);
g_value_set_double (&value, data);
clapper_features_bus_post_event (self->bus, self, event, &value, NULL);
}
static inline void
_post_boolean (ClapperFeaturesManager *self, ClapperFeaturesManagerEvent event, gboolean data)
{
GValue value = G_VALUE_INIT;
g_value_init (&value, G_TYPE_BOOLEAN);
g_value_set_boolean (&value, data);
clapper_features_bus_post_event (self->bus, self, event, &value, NULL);
}
static inline void
_post_item_added_or_removed (ClapperFeaturesManager *self, ClapperFeaturesManagerEvent event,
ClapperMediaItem *item, guint index)
{
GValue value = G_VALUE_INIT;
GValue extra_value = G_VALUE_INIT;
g_value_init (&value, G_TYPE_OBJECT);
g_value_set_object (&value, (GObject *) item);
g_value_init (&extra_value, G_TYPE_UINT);
g_value_set_uint (&extra_value, index);
clapper_features_bus_post_event (self->bus, self, event, &value, &extra_value);
}
static inline void
_post_item_reposition (ClapperFeaturesManager *self, guint data_1, guint data_2)
{
GValue value = G_VALUE_INIT;
GValue extra_value = G_VALUE_INIT;
g_value_init (&value, G_TYPE_UINT);
g_value_set_uint (&value, data_1);
g_value_init (&extra_value, G_TYPE_UINT);
g_value_set_uint (&extra_value, data_2);
clapper_features_bus_post_event (self->bus, self,
CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_REPOSITIONED, &value, &extra_value);
}
/*
* clapper_features_manager_new:
*
* Returns: (transfer full): a new #ClapperFeaturesManager instance.
*/
ClapperFeaturesManager *
clapper_features_manager_new (void)
{
ClapperFeaturesManager *features_manager;
features_manager = g_object_new (CLAPPER_TYPE_FEATURES_MANAGER, NULL);
gst_object_ref_sink (features_manager);
return features_manager;
}
void
clapper_features_manager_add_feature (ClapperFeaturesManager *self, ClapperFeature *feature, GstObject *parent)
{
GValue value = G_VALUE_INIT;
GValue extra_value = G_VALUE_INIT;
g_value_init (&value, G_TYPE_OBJECT);
g_value_set_object (&value, G_OBJECT (feature));
g_value_init (&extra_value, G_TYPE_OBJECT);
g_value_set_object (&extra_value, G_OBJECT (parent));
clapper_features_bus_post_event (self->bus, self,
CLAPPER_FEATURES_MANAGER_EVENT_FEATURE_ADDED, &value, &extra_value);
}
void
clapper_features_manager_trigger_property_changed (ClapperFeaturesManager *self, ClapperFeature *feature, GParamSpec *pspec)
{
GValue value = G_VALUE_INIT;
GValue extra_value = G_VALUE_INIT;
g_value_init (&value, G_TYPE_OBJECT);
g_value_set_object (&value, G_OBJECT (feature));
g_value_init (&extra_value, G_TYPE_PARAM);
g_value_set_param (&extra_value, pspec);
clapper_features_bus_post_event (self->bus, self,
CLAPPER_FEATURES_MANAGER_EVENT_FEATURE_PROPERTY_CHANGED, &value, &extra_value);
}
void
clapper_features_manager_trigger_state_changed (ClapperFeaturesManager *self, ClapperPlayerState state)
{
_post_int (self, CLAPPER_FEATURES_MANAGER_EVENT_STATE_CHANGED, state);
}
void
clapper_features_manager_trigger_position_changed (ClapperFeaturesManager *self, gdouble position)
{
_post_double (self, CLAPPER_FEATURES_MANAGER_EVENT_POSITION_CHANGED, position);
}
void
clapper_features_manager_trigger_speed_changed (ClapperFeaturesManager *self, gdouble speed)
{
_post_double (self, CLAPPER_FEATURES_MANAGER_EVENT_SPEED_CHANGED, speed);
}
void
clapper_features_manager_trigger_volume_changed (ClapperFeaturesManager *self, gdouble volume)
{
_post_double (self, CLAPPER_FEATURES_MANAGER_EVENT_VOLUME_CHANGED, volume);
}
void
clapper_features_manager_trigger_mute_changed (ClapperFeaturesManager *self, gboolean mute)
{
_post_boolean (self, CLAPPER_FEATURES_MANAGER_EVENT_MUTE_CHANGED, mute);
}
void
clapper_features_manager_trigger_played_item_changed (ClapperFeaturesManager *self, ClapperMediaItem *item)
{
_post_object (self, CLAPPER_FEATURES_MANAGER_EVENT_PLAYED_ITEM_CHANGED, (GObject *) item);
}
void
clapper_features_manager_trigger_item_updated (ClapperFeaturesManager *self, ClapperMediaItem *item)
{
_post_object (self, CLAPPER_FEATURES_MANAGER_EVENT_ITEM_UPDATED, (GObject *) item);
}
void
clapper_features_manager_trigger_queue_item_added (ClapperFeaturesManager *self, ClapperMediaItem *item, guint index)
{
_post_item_added_or_removed (self, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_ADDED, item, index);
}
void
clapper_features_manager_trigger_queue_item_removed (ClapperFeaturesManager *self, ClapperMediaItem *item, guint index)
{
_post_item_added_or_removed (self, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_REMOVED, item, index);
}
void
clapper_features_manager_trigger_queue_item_repositioned (ClapperFeaturesManager *self, guint before, guint after)
{
_post_item_reposition (self, before, after);
}
void
clapper_features_manager_trigger_queue_cleared (ClapperFeaturesManager *self)
{
clapper_features_bus_post_event (self->bus, self, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_CLEARED, NULL, NULL);
}
void
clapper_features_manager_trigger_queue_progression_changed (ClapperFeaturesManager *self, ClapperQueueProgressionMode mode)
{
_post_int (self, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_PROGRESSION_CHANGED, mode);
}
void
clapper_features_manager_handle_event (ClapperFeaturesManager *self, ClapperFeaturesManagerEvent event,
const GValue *value, const GValue *extra_value)
{
guint i;
switch (event) {
case CLAPPER_FEATURES_MANAGER_EVENT_FEATURE_ADDED:{
ClapperFeature *feature = g_value_get_object (value);
GstObject *parent = g_value_get_object (extra_value);
if (!g_ptr_array_find (self->features, feature, NULL)) {
g_ptr_array_add (self->features, gst_object_ref (feature));
gst_object_set_parent (GST_OBJECT_CAST (feature), parent);
clapper_feature_call_prepare (feature);
}
/* Nothing more to do */
return;
}
default:
break;
}
for (i = 0; i < self->features->len; ++i) {
ClapperFeature *feature = g_ptr_array_index (self->features, i);
switch (event) {
case CLAPPER_FEATURES_MANAGER_EVENT_FEATURE_PROPERTY_CHANGED:{
ClapperFeature *event_feature = g_value_get_object (value);
if (feature == event_feature) {
clapper_feature_call_property_changed (feature,
g_value_get_param (extra_value));
}
break;
}
case CLAPPER_FEATURES_MANAGER_EVENT_STATE_CHANGED:
clapper_feature_call_state_changed (feature, g_value_get_int (value));
break;
case CLAPPER_FEATURES_MANAGER_EVENT_POSITION_CHANGED:
clapper_feature_call_position_changed (feature, g_value_get_double (value));
break;
case CLAPPER_FEATURES_MANAGER_EVENT_SPEED_CHANGED:
clapper_feature_call_speed_changed (feature, g_value_get_double (value));
break;
case CLAPPER_FEATURES_MANAGER_EVENT_VOLUME_CHANGED:
clapper_feature_call_volume_changed (feature, g_value_get_double (value));
break;
case CLAPPER_FEATURES_MANAGER_EVENT_MUTE_CHANGED:
clapper_feature_call_mute_changed (feature, g_value_get_boolean (value));
break;
case CLAPPER_FEATURES_MANAGER_EVENT_PLAYED_ITEM_CHANGED:
clapper_feature_call_played_item_changed (feature,
CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)));
break;
case CLAPPER_FEATURES_MANAGER_EVENT_ITEM_UPDATED:
clapper_feature_call_item_updated (feature,
CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)));
break;
case CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_ADDED:
clapper_feature_call_queue_item_added (feature,
CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)),
g_value_get_uint (extra_value));
break;
case CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_REMOVED:
clapper_feature_call_queue_item_removed (feature,
CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)),
g_value_get_uint (extra_value));
break;
case CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_REPOSITIONED:
clapper_feature_call_queue_item_repositioned (feature,
g_value_get_uint (value),
g_value_get_uint (extra_value));
break;
case CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_CLEARED:
clapper_feature_call_queue_cleared (feature);
break;
case CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_PROGRESSION_CHANGED:
clapper_feature_call_queue_progression_changed (feature, g_value_get_int (value));
break;
default:
break;
}
}
}
static void
clapper_features_manager_thread_start (ClapperThreadedObject *threaded_object)
{
ClapperFeaturesManager *self = CLAPPER_FEATURES_MANAGER_CAST (threaded_object);
GST_TRACE_OBJECT (threaded_object, "Features manager thread start");
self->features = g_ptr_array_new_with_free_func (
(GDestroyNotify) gst_object_unref);
self->bus = clapper_features_bus_new ();
}
static void
clapper_features_manager_thread_stop (ClapperThreadedObject *threaded_object)
{
ClapperFeaturesManager *self = CLAPPER_FEATURES_MANAGER_CAST (threaded_object);
guint i;
GST_TRACE_OBJECT (threaded_object, "Features manager thread stop");
gst_bus_set_flushing (GST_BUS_CAST (self->bus), TRUE);
gst_bus_remove_watch (GST_BUS_CAST (self->bus));
gst_clear_object (&self->bus);
for (i = 0; i < self->features->len; ++i) {
ClapperFeature *feature = g_ptr_array_index (self->features, i);
clapper_feature_call_unprepare (feature);
gst_object_unparent (GST_OBJECT_CAST (feature));
}
g_ptr_array_unref (self->features);
}
static void
clapper_features_manager_init (ClapperFeaturesManager *self)
{
}
static void
clapper_features_manager_class_init (ClapperFeaturesManagerClass *klass)
{
ClapperThreadedObjectClass *threaded_object = (ClapperThreadedObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperfeaturesmanager", 0,
"Clapper Features Manager");
threaded_object->thread_start = clapper_features_manager_thread_start;
threaded_object->thread_stop = clapper_features_manager_thread_stop;
}

View File

@@ -0,0 +1,35 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include "clapper-enums.h"
#include "clapper-marker.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
ClapperMarker * clapper_marker_new_internal (ClapperMarkerType marker_type, const gchar *name, gdouble start, gdouble end);
G_GNUC_INTERNAL
gboolean clapper_marker_is_internal (ClapperMarker *marker);
G_END_DECLS

View File

@@ -0,0 +1,338 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperMarker:
*
* Represents a point in timeline.
*
* Markers are a convienient way of marking points of interest within a
* [class@Clapper.Timeline] of [class@Clapper.MediaItem]. Use them
* to indicate certain areas on the timeline.
*
* Markers are reference counted immutable objects. Once a marker is created
* it can only be inserted into a single [class@Clapper.Timeline] at a time.
*
* Please note that markers are independent of [property@Clapper.MediaItem:duration]
* and applications should not assume that all markers must have start/end times
* lower or equal the item duration. This is not the case in e.g. live streams
* where duration is unknown, but markers are still allowed to mark entries
* (like EPG titles for example).
*
* Remember that [class@Clapper.Player] will also automatically insert certain
* markers extracted from media such as video chapters. Clapper will never
* "touch" the ones created by the application. If you want to differentiate
* your own markers, applications can define and create markers with one of
* the custom types from [enum@Clapper.MarkerType] enum.
*
* Example:
*
* ```c
* #define MY_APP_MARKER (CLAPPER_MARKER_TYPE_CUSTOM_1)
*
* ClapperMarker *marker = clapper_marker_new (MY_APP_MARKER, title, start, end);
* ```
*
* ```c
* ClapperMarkerType marker_type = clapper_marker_get_marker_type (marker);
*
* if (marker_type == MY_APP_MARKER) {
* // Do something with your custom marker
* }
* ```
*/
#include "clapper-marker-private.h"
#define GST_CAT_DEFAULT clapper_marker_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperMarker
{
GstObject parent;
ClapperMarkerType marker_type;
gchar *title;
gdouble start;
gdouble end;
gboolean is_internal;
};
enum
{
PROP_0,
PROP_MARKER_TYPE,
PROP_TITLE,
PROP_START,
PROP_END,
PROP_LAST
};
#define parent_class clapper_marker_parent_class
G_DEFINE_TYPE (ClapperMarker, clapper_marker, GST_TYPE_OBJECT);
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
/**
* clapper_marker_new:
* @marker_type: a #ClapperMarkerType
* @title: (nullable): title of the marker
* @start: a start position of the marker
* @end: an end position of the marker or [const@Clapper.MARKER_NO_END] if none
*
* Creates a new #ClapperMarker with given params.
*
* It is considered a programmer error trying to set an ending
* point that is before the starting one. If end is unknown or
* not defined a special [const@Clapper.MARKER_NO_END] value
* should be used.
*
* Returns: (transfer full): a new #ClapperMarker.
*/
ClapperMarker *
clapper_marker_new (ClapperMarkerType marker_type, const gchar *title,
gdouble start, gdouble end)
{
ClapperMarker *marker;
marker = g_object_new (CLAPPER_TYPE_MARKER,
"marker-type", marker_type,
"title", title,
"start", start,
"end", end, NULL);
gst_object_ref_sink (marker);
return marker;
}
ClapperMarker *
clapper_marker_new_internal (ClapperMarkerType marker_type, const gchar *title,
gdouble start, gdouble end)
{
ClapperMarker *marker;
marker = clapper_marker_new (marker_type, title, start, end);
marker->is_internal = TRUE;
return marker;
}
/**
* clapper_marker_get_marker_type:
* @marker: a #ClapperMarker
*
* Get the #ClapperMarkerType of @marker.
*
* Returns: type of marker.
*/
ClapperMarkerType
clapper_marker_get_marker_type (ClapperMarker *self)
{
g_return_val_if_fail (CLAPPER_IS_MARKER (self), CLAPPER_MARKER_TYPE_UNKNOWN);
return self->marker_type;
}
/**
* clapper_marker_get_title:
* @marker: a #ClapperMarker
*
* Get the title of @marker.
*
* Returns: the marker title.
*/
const gchar *
clapper_marker_get_title (ClapperMarker *self)
{
g_return_val_if_fail (CLAPPER_IS_MARKER (self), NULL);
return self->title;
}
/**
* clapper_marker_get_start:
* @marker: a #ClapperMarker
*
* Get the start position (in seconds) of @marker.
*
* Returns: marker start.
*/
gdouble
clapper_marker_get_start (ClapperMarker *self)
{
g_return_val_if_fail (CLAPPER_IS_MARKER (self), 0);
return self->start;
}
/**
* clapper_marker_get_end:
* @marker: a #ClapperMarker
*
* Get the end position (in seconds) of @marker.
*
* Returns: marker end.
*/
gdouble
clapper_marker_get_end (ClapperMarker *self)
{
g_return_val_if_fail (CLAPPER_IS_MARKER (self), CLAPPER_MARKER_NO_END);
return self->end;
}
gboolean
clapper_marker_is_internal (ClapperMarker *self)
{
return self->is_internal;
}
static void
clapper_marker_init (ClapperMarker *self)
{
self->marker_type = CLAPPER_MARKER_TYPE_UNKNOWN;
self->end = CLAPPER_MARKER_NO_END;
}
static void
clapper_marker_constructed (GObject *object)
{
ClapperMarker *self = CLAPPER_MARKER_CAST (object);
G_OBJECT_CLASS (parent_class)->constructed (object);
GST_TRACE_OBJECT (self, "Created new marker"
", type: %i, title: \"%s\", start: %lf, end: %lf",
self->marker_type, GST_STR_NULL (self->title), self->start, self->end);
}
static void
clapper_marker_finalize (GObject *object)
{
ClapperMarker *self = CLAPPER_MARKER_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
g_free (self->title);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_marker_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperMarker *self = CLAPPER_MARKER_CAST (object);
switch (prop_id) {
case PROP_MARKER_TYPE:
g_value_set_enum (value, clapper_marker_get_marker_type (self));
break;
case PROP_TITLE:
g_value_set_string (value, clapper_marker_get_title (self));
break;
case PROP_START:
g_value_set_double (value, clapper_marker_get_start (self));
break;
case PROP_END:
g_value_set_double (value, clapper_marker_get_end (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_marker_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
ClapperMarker *self = CLAPPER_MARKER_CAST (object);
switch (prop_id) {
case PROP_MARKER_TYPE:
self->marker_type = g_value_get_enum (value);
break;
case PROP_TITLE:
self->title = g_value_dup_string (value);
break;
case PROP_START:
self->start = g_value_get_double (value);
break;
case PROP_END:
self->end = g_value_get_double (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_marker_class_init (ClapperMarkerClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappermarker", 0,
"Clapper Marker");
gobject_class->constructed = clapper_marker_constructed;
gobject_class->get_property = clapper_marker_get_property;
gobject_class->set_property = clapper_marker_set_property;
gobject_class->finalize = clapper_marker_finalize;
/**
* ClapperMarker:marker-type:
*
* Type of stream.
*/
param_specs[PROP_MARKER_TYPE] = g_param_spec_enum ("marker-type",
NULL, NULL, CLAPPER_TYPE_MARKER_TYPE, CLAPPER_MARKER_TYPE_UNKNOWN,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperMarker:title:
*
* Title of marker.
*/
param_specs[PROP_TITLE] = g_param_spec_string ("title",
NULL, NULL, NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperMarker:start:
*
* Starting time of marker.
*/
param_specs[PROP_START] = g_param_spec_double ("start",
NULL, NULL, 0, G_MAXDOUBLE, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperMarker:end:
*
* Ending time of marker.
*/
param_specs[PROP_END] = g_param_spec_double ("end",
NULL, NULL, -1, G_MAXDOUBLE, CLAPPER_MARKER_NO_END,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
}

View File

@@ -0,0 +1,58 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <clapper/clapper-enums.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_MARKER (clapper_marker_get_type())
#define CLAPPER_MARKER_CAST(obj) ((ClapperMarker *)(obj))
/* NOTE: #ClapperMarker are immutable objects that cannot be derived,
* otherwise #ClapperFeaturesManager would not be able to announce media
* item changed caused by changes within them */
G_DECLARE_FINAL_TYPE (ClapperMarker, clapper_marker, CLAPPER, MARKER, GstObject)
/**
* CLAPPER_MARKER_NO_END:
*
* The value used to indicate that marker does not have an ending time specified
*/
#define CLAPPER_MARKER_NO_END (-1.0)
ClapperMarker * clapper_marker_new (ClapperMarkerType marker_type, const gchar *title, gdouble start, gdouble end);
ClapperMarkerType clapper_marker_get_marker_type (ClapperMarker *marker);
const gchar * clapper_marker_get_title (ClapperMarker *marker);
gdouble clapper_marker_get_start (ClapperMarker *marker);
gdouble clapper_marker_get_end (ClapperMarker *marker);
G_END_DECLS

View File

@@ -0,0 +1,46 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gst/pbutils/pbutils.h>
#include "clapper-media-item.h"
#include "clapper-player-private.h"
#include "clapper-app-bus-private.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
void clapper_media_item_update_from_tag_list (ClapperMediaItem *item, const GstTagList *tags, ClapperPlayer *player);
G_GNUC_INTERNAL
void clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDiscovererInfo *info);
G_GNUC_INTERNAL
gboolean clapper_media_item_set_duration (ClapperMediaItem *item, gdouble duration, ClapperAppBus *app_bus);
G_GNUC_INTERNAL
void clapper_media_item_set_used (ClapperMediaItem *item, gboolean used);
G_GNUC_INTERNAL
gboolean clapper_media_item_get_used (ClapperMediaItem *item);
G_END_DECLS

View File

@@ -0,0 +1,641 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperMediaItem:
*
* Represents a media item.
*
* A newly created media item must be added to player [class@Clapper.Queue]
* first in order to be played.
*/
#include "clapper-media-item-private.h"
#include "clapper-timeline-private.h"
#include "clapper-player-private.h"
#include "clapper-playbin-bus-private.h"
#include "clapper-features-manager-private.h"
#include "clapper-utils-private.h"
#define GST_CAT_DEFAULT clapper_media_item_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperMediaItem
{
GstObject parent;
gchar *uri;
gchar *suburi;
ClapperTimeline *timeline;
guint id;
gchar *title;
gchar *container_format;
gdouble duration;
/* For shuffle */
gboolean used;
};
enum
{
PROP_0,
PROP_ID,
PROP_URI,
PROP_SUBURI,
PROP_TITLE,
PROP_CONTAINER_FORMAT,
PROP_DURATION,
PROP_TIMELINE,
PROP_LAST
};
#define parent_class clapper_media_item_parent_class
G_DEFINE_TYPE (ClapperMediaItem, clapper_media_item, GST_TYPE_OBJECT);
static guint _item_id = 0;
static GMutex id_lock;
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
/**
* clapper_media_item_new:
* @uri: a media URI
*
* Creates new #ClapperMediaItem from URI.
*
* Use one of the URI protocols supported by plugins in #GStreamer
* installation. For local files you can use either "file" protocol
* or [ctor@Clapper.MediaItem.new_from_file] method.
*
* It is considered a programmer error trying to create new media item from
* invalid URI. If URI is valid, but unsupported by installed plugins on user
* system, [class@Clapper.Player] will emit a [signal@Clapper.Player::missing-plugin]
* signal upon playback.
*
* Returns: (transfer full): a new #ClapperMediaItem.
*/
ClapperMediaItem *
clapper_media_item_new (const gchar *uri)
{
ClapperMediaItem *item;
g_return_val_if_fail (uri != NULL, NULL);
item = g_object_new (CLAPPER_TYPE_MEDIA_ITEM, "uri", uri, NULL);
gst_object_ref_sink (item);
g_mutex_lock (&id_lock);
item->id = _item_id;
_item_id++;
g_mutex_unlock (&id_lock);
/* FIXME: Set initial container format from file extension parsing */
GST_TRACE_OBJECT (item, "New media item, ID: %u, URI: %s, title: %s",
item->id, item->uri, item->title);
return item;
}
/**
* clapper_media_item_new_from_file:
* @file: a #GFile
*
* Creates new #ClapperMediaItem from #GFile.
*
* Same as [ctor@Clapper.MediaItem.new], but uses a [iface@Gio.File]
* for convenience in some situations instead of an URI.
*
* Returns: (transfer full): a new #ClapperMediaItem.
*/
ClapperMediaItem *
clapper_media_item_new_from_file (GFile *file)
{
ClapperMediaItem *item;
gchar *uri;
g_return_val_if_fail (G_IS_FILE (file), NULL);
uri = clapper_utils_uri_from_file (file);
item = clapper_media_item_new (uri);
g_free (uri);
return item;
}
/**
* clapper_media_item_get_id:
* @item: a #ClapperMediaItem
*
* Get the unique ID of #ClapperMediaItem.
*
* Returns: an ID of #ClapperMediaItem.
*/
guint
clapper_media_item_get_id (ClapperMediaItem *self)
{
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), G_MAXUINT);
return self->id;
}
/**
* clapper_media_item_get_uri:
* @item: a #ClapperMediaItem
*
* Get the URI of #ClapperMediaItem.
*
* Returns: an URI of #ClapperMediaItem.
*/
const gchar *
clapper_media_item_get_uri (ClapperMediaItem *self)
{
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL);
return self->uri;
}
/**
* clapper_media_item_set_suburi:
* @item: a #ClapperMediaItem
*
* Set the additional URI of #ClapperMediaItem.
*
* This is typically used to add an external subtitles URI
* to the @item.
*/
void
clapper_media_item_set_suburi (ClapperMediaItem *self, const gchar *suburi)
{
gboolean changed;
GST_OBJECT_LOCK (self);
changed = g_set_str (&self->suburi, suburi);
GST_OBJECT_UNLOCK (self);
if (changed) {
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
if (player) {
clapper_app_bus_post_prop_notify (player->app_bus,
GST_OBJECT_CAST (self), param_specs[PROP_SUBURI]);
clapper_playbin_bus_post_item_suburi_change (player->bus, self);
gst_object_unref (player);
}
}
}
/**
* clapper_media_item_get_suburi:
* @item: a #ClapperMediaItem
*
* Get the additional URI of #ClapperMediaItem.
*
* Returns: (transfer full) (nullable): an additional URI of #ClapperMediaItem.
*/
gchar *
clapper_media_item_get_suburi (ClapperMediaItem *self)
{
gchar *suburi;
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL);
GST_OBJECT_LOCK (self);
suburi = g_strdup (self->suburi);
GST_OBJECT_UNLOCK (self);
return suburi;
}
static gboolean
clapper_media_item_take_title (ClapperMediaItem *self, gchar *title,
ClapperAppBus *app_bus)
{
gboolean changed;
GST_OBJECT_LOCK (self);
if ((changed = g_strcmp0 (self->title, title) != 0)) {
g_free (self->title);
self->title = title;
}
GST_OBJECT_UNLOCK (self);
if (changed)
clapper_app_bus_post_prop_notify (app_bus, GST_OBJECT_CAST (self), param_specs[PROP_TITLE]);
else
g_free (title);
return changed;
}
/**
* clapper_media_item_get_title:
* @item: a #ClapperMediaItem
*
* Get media item title.
*
* The title can be either text detected by media discovery once it
* completes. Otherwise whenever possible this will try to return a title
* extracted from media URI e.g. basename without extension for local files.
*
* Returns: (transfer full) (nullable): media title.
*/
gchar *
clapper_media_item_get_title (ClapperMediaItem *self)
{
gchar *title;
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL);
GST_OBJECT_LOCK (self);
title = g_strdup (self->title);
GST_OBJECT_UNLOCK (self);
return title;
}
static gboolean
clapper_media_item_take_container_format (ClapperMediaItem *self, gchar *container_format,
ClapperAppBus *app_bus)
{
gboolean changed;
GST_OBJECT_LOCK (self);
if ((changed = g_strcmp0 (self->container_format, container_format) != 0)) {
g_free (self->container_format);
self->container_format = container_format;
}
GST_OBJECT_UNLOCK (self);
if (changed)
clapper_app_bus_post_prop_notify (app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CONTAINER_FORMAT]);
else
g_free (container_format);
return changed;
}
/**
* clapper_media_item_get_container_format:
* @item: a #ClapperMediaItem
*
* Get media item container format.
*
* Returns: (transfer full) (nullable): media container format.
*/
gchar *
clapper_media_item_get_container_format (ClapperMediaItem *self)
{
gchar *container_format;
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL);
GST_OBJECT_LOCK (self);
container_format = g_strdup (self->container_format);
GST_OBJECT_UNLOCK (self);
return container_format;
}
gboolean
clapper_media_item_set_duration (ClapperMediaItem *self, gdouble duration,
ClapperAppBus *app_bus)
{
gboolean changed;
GST_OBJECT_LOCK (self);
if ((changed = !G_APPROX_VALUE (self->duration, duration, FLT_EPSILON)))
self->duration = duration;
GST_OBJECT_UNLOCK (self);
if (changed) {
GST_DEBUG_OBJECT (self, "Duration: %" GST_TIME_FORMAT, GST_TIME_ARGS (duration * GST_SECOND));
clapper_app_bus_post_prop_notify (app_bus, GST_OBJECT_CAST (self), param_specs[PROP_DURATION]);
}
return changed;
}
/**
* clapper_media_item_get_duration:
* @item: a #ClapperMediaItem
*
* Get media item duration as decimal number in seconds.
*
* Returns: media duration.
*/
gdouble
clapper_media_item_get_duration (ClapperMediaItem *self)
{
gdouble duration;
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), 0);
GST_OBJECT_LOCK (self);
duration = self->duration;
GST_OBJECT_UNLOCK (self);
return duration;
}
/**
* clapper_media_item_get_timeline:
* @item: a #ClapperMediaItem
*
* Get the [class@Clapper.Timeline] assosiated with @item.
*
* Returns: (transfer none): a #ClapperTimeline of item.
*/
ClapperTimeline *
clapper_media_item_get_timeline (ClapperMediaItem *self)
{
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL);
return self->timeline;
}
static gboolean
clapper_media_item_update_from_container_tags (ClapperMediaItem *self, const GstTagList *tags,
ClapperAppBus *app_bus)
{
gchar *string = NULL;
gboolean changed = FALSE;
if (gst_tag_list_get_string (tags, GST_TAG_CONTAINER_FORMAT, &string))
changed |= clapper_media_item_take_container_format (self, string, app_bus);
if (gst_tag_list_get_string (tags, GST_TAG_TITLE, &string))
changed |= clapper_media_item_take_title (self, string, app_bus);
return changed;
}
void
clapper_media_item_update_from_tag_list (ClapperMediaItem *self, const GstTagList *tags,
ClapperPlayer *player)
{
GstTagScope scope = gst_tag_list_get_scope (tags);
if (scope == GST_TAG_SCOPE_GLOBAL) {
gboolean changed = clapper_media_item_update_from_container_tags (self, tags, player->app_bus);
if (changed) {
ClapperFeaturesManager *features_manager;
if ((features_manager = clapper_player_get_features_manager (player)))
clapper_features_manager_trigger_item_updated (features_manager, self);
}
}
}
void
clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDiscovererInfo *info)
{
ClapperPlayer *player;
GstDiscovererStreamInfo *sinfo;
GstClockTime duration;
gdouble val_dbl;
gboolean changed = FALSE;
if (!(player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self))))
return;
for (sinfo = gst_discoverer_info_get_stream_info (info);
sinfo != NULL;
sinfo = gst_discoverer_stream_info_get_next (sinfo)) {
const GstTagList *tags;
if (GST_IS_DISCOVERER_CONTAINER_INFO (sinfo)) {
GstDiscovererContainerInfo *cinfo = (GstDiscovererContainerInfo *) sinfo;
if ((tags = gst_discoverer_container_info_get_tags (cinfo)))
changed |= clapper_media_item_update_from_container_tags (self, tags, player->app_bus);
}
gst_discoverer_stream_info_unref (sinfo);
}
duration = gst_discoverer_info_get_duration (info);
if (G_UNLIKELY (duration == GST_CLOCK_TIME_NONE))
duration = 0;
val_dbl = (gdouble) duration / GST_SECOND;
changed |= clapper_media_item_set_duration (self, val_dbl, player->app_bus);
if (changed) {
ClapperFeaturesManager *features_manager;
if ((features_manager = clapper_player_get_features_manager (player)))
clapper_features_manager_trigger_item_updated (features_manager, self);
}
gst_object_unref (player);
}
void
clapper_media_item_set_used (ClapperMediaItem *self, gboolean used)
{
GST_OBJECT_LOCK (self);
self->used = used;
GST_OBJECT_UNLOCK (self);
}
gboolean
clapper_media_item_get_used (ClapperMediaItem *self)
{
gboolean used;
GST_OBJECT_LOCK (self);
used = self->used;
GST_OBJECT_UNLOCK (self);
return used;
}
static void
clapper_media_item_init (ClapperMediaItem *self)
{
self->timeline = clapper_timeline_new ();
gst_object_set_parent (GST_OBJECT_CAST (self->timeline), GST_OBJECT_CAST (self));
}
static void
clapper_media_item_constructed (GObject *object)
{
ClapperMediaItem *self = CLAPPER_MEDIA_ITEM_CAST (object);
/* Be safe when someone incorrectly constructs item without URI */
if (G_UNLIKELY (self->uri == NULL))
self->uri = g_strdup ("file://");
self->title = clapper_utils_title_from_uri (self->uri);
G_OBJECT_CLASS (parent_class)->constructed (object);
}
static void
clapper_media_item_finalize (GObject *object)
{
ClapperMediaItem *self = CLAPPER_MEDIA_ITEM_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
g_free (self->uri);
g_free (self->title);
g_free (self->container_format);
gst_object_unparent (GST_OBJECT_CAST (self->timeline));
gst_object_unref (self->timeline);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_media_item_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
ClapperMediaItem *self = CLAPPER_MEDIA_ITEM_CAST (object);
switch (prop_id) {
case PROP_URI:
self->uri = g_value_dup_string (value);
break;
case PROP_SUBURI:
clapper_media_item_set_suburi (self, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_media_item_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperMediaItem *self = CLAPPER_MEDIA_ITEM_CAST (object);
switch (prop_id) {
case PROP_ID:
g_value_set_uint (value, clapper_media_item_get_id (self));
break;
case PROP_URI:
g_value_set_string (value, clapper_media_item_get_uri (self));
break;
case PROP_SUBURI:
g_value_take_string (value, clapper_media_item_get_suburi (self));
break;
case PROP_TITLE:
g_value_take_string (value, clapper_media_item_get_title (self));
break;
case PROP_CONTAINER_FORMAT:
g_value_take_string (value, clapper_media_item_get_container_format (self));
break;
case PROP_DURATION:
g_value_set_double (value, clapper_media_item_get_duration (self));
break;
case PROP_TIMELINE:
g_value_set_object (value, clapper_media_item_get_timeline (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_media_item_class_init (ClapperMediaItemClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappermediaitem", 0,
"Clapper Media Item");
gobject_class->constructed = clapper_media_item_constructed;
gobject_class->set_property = clapper_media_item_set_property;
gobject_class->get_property = clapper_media_item_get_property;
gobject_class->finalize = clapper_media_item_finalize;
/**
* ClapperMediaItem:id:
*
* Media Item ID.
*/
param_specs[PROP_ID] = g_param_spec_uint ("id",
NULL, NULL, 0, G_MAXUINT, G_MAXUINT,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperMediaItem:uri:
*
* Media URI.
*/
param_specs[PROP_URI] = g_param_spec_string ("uri",
NULL, NULL, NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperMediaItem:suburi:
*
* Media additional URI.
*/
param_specs[PROP_SUBURI] = g_param_spec_string ("suburi",
NULL, NULL, NULL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperMediaItem:title:
*
* Media title.
*/
param_specs[PROP_TITLE] = g_param_spec_string ("title",
NULL, NULL, NULL,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperMediaItem:container-format:
*
* Media container format.
*/
param_specs[PROP_CONTAINER_FORMAT] = g_param_spec_string ("container-format",
NULL, NULL, NULL,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperMediaItem:duration:
*
* Media duration as a decimal number in seconds.
*/
param_specs[PROP_DURATION] = g_param_spec_double ("duration",
NULL, NULL, 0, G_MAXDOUBLE, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperPlayer:timeline:
*
* Media timeline.
*/
param_specs[PROP_TIMELINE] = g_param_spec_object ("timeline",
NULL, NULL, CLAPPER_TYPE_TIMELINE,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
}

View File

@@ -0,0 +1,60 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
#include <gst/gst.h>
#include <clapper/clapper-timeline.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_MEDIA_ITEM (clapper_media_item_get_type())
#define CLAPPER_MEDIA_ITEM_CAST(obj) ((ClapperMediaItem *)(obj))
G_DECLARE_FINAL_TYPE (ClapperMediaItem, clapper_media_item, CLAPPER, MEDIA_ITEM, GstObject)
ClapperMediaItem * clapper_media_item_new (const gchar *uri);
ClapperMediaItem * clapper_media_item_new_from_file (GFile *file);
guint clapper_media_item_get_id (ClapperMediaItem *item);
const gchar * clapper_media_item_get_uri (ClapperMediaItem *item);
void clapper_media_item_set_suburi (ClapperMediaItem *item, const gchar *suburi);
gchar * clapper_media_item_get_suburi (ClapperMediaItem *item);
gchar * clapper_media_item_get_title (ClapperMediaItem *item);
gchar * clapper_media_item_get_container_format (ClapperMediaItem *item);
gdouble clapper_media_item_get_duration (ClapperMediaItem *item);
ClapperTimeline * clapper_media_item_get_timeline (ClapperMediaItem *item);
G_END_DECLS

View File

@@ -0,0 +1,53 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gst/gst.h>
#include "clapper-enums-private.h"
#include "clapper-player.h"
#include "clapper-media-item.h"
G_BEGIN_DECLS
void clapper_playbin_bus_initialize (void);
gboolean clapper_playbin_bus_message_func (GstBus *bus, GstMessage *msg, ClapperPlayer *player);
void clapper_playbin_bus_post_set_volume (GstBus *bus, GstElement *playbin, gdouble volume);
void clapper_playbin_bus_post_set_prop (GstBus *bus, GstObject *src, const gchar *name, GValue *value);
void clapper_playbin_bus_post_set_play_flag (GstBus *bus, ClapperPlayerPlayFlags flag, gboolean enabled);
void clapper_playbin_bus_post_request_state (GstBus *bus, ClapperPlayer *player, GstState state);
void clapper_playbin_bus_post_seek (GstBus *bus, gdouble position, ClapperPlayerSeekMethod flags);
void clapper_playbin_bus_post_rate_change (GstBus *bus, gdouble rate);
void clapper_playbin_bus_post_stream_change (GstBus *bus);
void clapper_playbin_bus_post_current_item_change (GstBus *bus, ClapperMediaItem *current_item, ClapperQueueItemChangeMode mode);
void clapper_playbin_bus_post_item_suburi_change (GstBus *bus, ClapperMediaItem *item);
G_END_DECLS

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,138 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include "clapper-player.h"
#include "clapper-queue.h"
#include "clapper-enums.h"
#include "clapper-app-bus-private.h"
#include "clapper-features-manager-private.h"
G_BEGIN_DECLS
#define clapper_player_set_have_features(player,have) (g_atomic_int_set (&player->have_features, (gint) have))
#define clapper_player_get_have_features(player) (g_atomic_int_get (&player->have_features) == 1)
#define clapper_player_get_features_manager(player) (clapper_player_get_have_features(player) ? player->features_manager : NULL)
struct _ClapperPlayer
{
ClapperThreadedObject parent;
ClapperQueue *queue;
ClapperStreamList *video_streams;
ClapperStreamList *audio_streams;
ClapperStreamList *subtitle_streams;
ClapperFeaturesManager *features_manager;
gint have_features; // atomic integer
/* This is different from queue current item as it is used/changed only
* on player thread, so we can always update correct item without lock */
ClapperMediaItem *played_item;
/* Will eventually become our "played_item", can be set from
* different thread, thus needs a lock */
ClapperMediaItem *pending_item;
GstElement *playbin;
GstBus *bus;
ClapperAppBus *app_bus;
GSource *tick_source;
GstQuery *position_query;
/* Must only be used from player thread */
GstState current_state; // reported from playbin
GstState target_state; // state requested by user
gboolean is_buffering;
gdouble pending_position; // store seek before playback
gdouble requested_speed, pending_speed; // store speed for consecutive rate changes
/* Stream collection */
GstStreamCollection *collection;
gulong stream_notify_id;
/* Extra params */
gboolean use_playbin3; // when using playbin3
gboolean had_error; // so we do not do stuff after error
gboolean seeking; // during seek operation
gboolean speed_changing; // during rate change operation
gboolean pending_eos; // when pausing due to EOS
gint eos; // atomic integer
/* Playbin2 compat */
gint n_video, n_audio, n_text;
/* Props */
gboolean autoplay;
gboolean mute;
gdouble volume;
gdouble speed;
gdouble position;
ClapperPlayerState state;
GstElement *video_decoder;
GstElement *audio_decoder;
gboolean video_enabled;
gboolean audio_enabled;
gboolean subtitles_enabled;
gdouble audio_offset;
gdouble subtitle_offset;
};
ClapperPlayer * clapper_player_get_from_ancestor (GstObject *object);
gboolean clapper_player_refresh_position (ClapperPlayer *player);
void clapper_player_add_tick_source (ClapperPlayer *player);
void clapper_player_remove_tick_source (ClapperPlayer *player);
void clapper_player_handle_playbin_state_changed (ClapperPlayer *player);
void clapper_player_handle_playbin_volume_changed (ClapperPlayer *player, const GValue *value);
void clapper_player_handle_playbin_mute_changed (ClapperPlayer *player, const GValue *value);
void clapper_player_handle_playbin_flags_changed (ClapperPlayer *player, const GValue *value);
void clapper_player_handle_playbin_av_offset_changed (ClapperPlayer *player, const GValue *value);
void clapper_player_handle_playbin_text_offset_changed (ClapperPlayer *player, const GValue *value);
void clapper_player_handle_playbin_common_prop_changed (ClapperPlayer *player, const gchar *prop_name);
void clapper_player_handle_playbin_rate_changed (ClapperPlayer *player, gdouble speed);
void clapper_player_set_pending_item (ClapperPlayer *player, ClapperMediaItem *pending_item, ClapperQueueItemChangeMode mode);
void clapper_player_take_stream_collection (ClapperPlayer *player, GstStreamCollection *collection);
void clapper_player_refresh_streams (ClapperPlayer *player);
gboolean clapper_player_find_active_decoder_with_stream_id (ClapperPlayer *player, GstElementFactoryListType type, const gchar *stream_id);
void clapper_player_playbin_update_current_decoders (ClapperPlayer *player);
void clapper_player_reset (ClapperPlayer *player, gboolean pending_dispose);
G_END_DECLS

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,128 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <clapper/clapper-threaded-object.h>
#include <clapper/clapper-queue.h>
#include <clapper/clapper-stream-list.h>
#include <clapper/clapper-feature.h>
#include <clapper/clapper-enums.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_PLAYER (clapper_player_get_type())
#define CLAPPER_PLAYER_CAST(obj) ((ClapperPlayer *)(obj))
G_DECLARE_FINAL_TYPE (ClapperPlayer, clapper_player, CLAPPER, PLAYER, ClapperThreadedObject)
ClapperPlayer * clapper_player_new (void);
ClapperQueue * clapper_player_get_queue (ClapperPlayer *player);
ClapperStreamList * clapper_player_get_video_streams (ClapperPlayer *player);
ClapperStreamList * clapper_player_get_audio_streams (ClapperPlayer *player);
ClapperStreamList * clapper_player_get_subtitle_streams (ClapperPlayer *player);
void clapper_player_set_autoplay (ClapperPlayer *player, gboolean enabled);
gboolean clapper_player_get_autoplay (ClapperPlayer *player);
gdouble clapper_player_get_position (ClapperPlayer *player);
ClapperPlayerState clapper_player_get_state (ClapperPlayer *player);
void clapper_player_set_mute (ClapperPlayer *player, gboolean mute);
gboolean clapper_player_get_mute (ClapperPlayer *player);
void clapper_player_set_volume (ClapperPlayer *player, gdouble volume);
gdouble clapper_player_get_volume (ClapperPlayer *player);
void clapper_player_set_speed (ClapperPlayer *player, gdouble speed);
gdouble clapper_player_get_speed (ClapperPlayer *player);
void clapper_player_set_video_sink (ClapperPlayer *player, GstElement *element);
GstElement * clapper_player_get_video_sink (ClapperPlayer *player);
void clapper_player_set_audio_sink (ClapperPlayer *player, GstElement *element);
GstElement * clapper_player_get_audio_sink (ClapperPlayer *player);
void clapper_player_set_video_filter (ClapperPlayer *player, GstElement *element);
GstElement * clapper_player_get_video_filter (ClapperPlayer *player);
void clapper_player_set_audio_filter (ClapperPlayer *player, GstElement *element);
GstElement * clapper_player_get_audio_filter (ClapperPlayer *player);
GstElement * clapper_player_get_current_video_decoder (ClapperPlayer *player);
GstElement * clapper_player_get_current_audio_decoder (ClapperPlayer *player);
void clapper_player_set_video_enabled (ClapperPlayer *player, gboolean enabled);
gboolean clapper_player_get_video_enabled (ClapperPlayer *player);
void clapper_player_set_audio_enabled (ClapperPlayer *player, gboolean enabled);
gboolean clapper_player_get_audio_enabled (ClapperPlayer *player);
void clapper_player_set_subtitles_enabled (ClapperPlayer *player, gboolean enabled);
gboolean clapper_player_get_subtitles_enabled (ClapperPlayer *player);
void clapper_player_set_audio_offset (ClapperPlayer *player, gdouble offset);
gdouble clapper_player_get_audio_offset (ClapperPlayer *player);
void clapper_player_set_subtitle_offset (ClapperPlayer *player, gdouble offset);
gdouble clapper_player_get_subtitle_offset (ClapperPlayer *player);
void clapper_player_set_subtitle_font_desc (ClapperPlayer *player, const gchar *font_desc);
gchar * clapper_player_get_subtitle_font_desc (ClapperPlayer *player);
void clapper_player_play (ClapperPlayer *player);
void clapper_player_pause (ClapperPlayer *player);
void clapper_player_stop (ClapperPlayer *player);
void clapper_player_seek (ClapperPlayer *player, gdouble position);
void clapper_player_seek_custom (ClapperPlayer *player, gdouble position, ClapperPlayerSeekMethod method);
void clapper_player_add_feature (ClapperPlayer *player, ClapperFeature *feature);
G_END_DECLS

View File

@@ -0,0 +1,39 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include "clapper-queue.h"
#include "clapper-media-item.h"
#include "clapper-player.h"
#include "clapper-app-bus-private.h"
G_BEGIN_DECLS
ClapperQueue * clapper_queue_new (void);
void clapper_queue_handle_played_item_changed (ClapperQueue *queue, ClapperMediaItem *played_item, ClapperAppBus *app_bus);
void clapper_queue_handle_about_to_finish (ClapperQueue *queue, ClapperPlayer *player);
gboolean clapper_queue_handle_eos (ClapperQueue *queue, ClapperPlayer *player);
G_END_DECLS

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <clapper/clapper-enums.h>
#include <clapper/clapper-media-item.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_QUEUE (clapper_queue_get_type())
#define CLAPPER_QUEUE_CAST(obj) ((ClapperQueue *)(obj))
G_DECLARE_FINAL_TYPE (ClapperQueue, clapper_queue, CLAPPER, QUEUE, GstObject)
/**
* CLAPPER_QUEUE_INVALID_POSITION:
*
* The value used to refer to an invalid position in a #ClapperQueue
*/
#define CLAPPER_QUEUE_INVALID_POSITION ((guint) 0xffffffff)
void clapper_queue_add_item (ClapperQueue *queue, ClapperMediaItem *item);
void clapper_queue_insert_item (ClapperQueue *queue, ClapperMediaItem *item, gint index);
void clapper_queue_reposition_item (ClapperQueue *queue, ClapperMediaItem *item, gint index);
void clapper_queue_remove_item (ClapperQueue *queue, ClapperMediaItem *item);
void clapper_queue_remove_index (ClapperQueue *queue, guint index);
ClapperMediaItem * clapper_queue_steal_index (ClapperQueue *queue, guint index);
void clapper_queue_clear (ClapperQueue *queue);
gboolean clapper_queue_select_item (ClapperQueue *queue, ClapperMediaItem *item);
gboolean clapper_queue_select_index (ClapperQueue *queue, guint index);
gboolean clapper_queue_select_next_item (ClapperQueue *queue);
gboolean clapper_queue_select_previous_item (ClapperQueue *queue);
ClapperMediaItem * clapper_queue_get_item (ClapperQueue *queue, guint index);
ClapperMediaItem * clapper_queue_get_current_item (ClapperQueue *queue);
guint clapper_queue_get_current_index (ClapperQueue *queue);
gboolean clapper_queue_item_is_current (ClapperQueue *queue, ClapperMediaItem *item);
gboolean clapper_queue_find_item (ClapperQueue *queue, ClapperMediaItem *item, guint *index);
guint clapper_queue_get_n_items (ClapperQueue *queue);
void clapper_queue_set_progression_mode (ClapperQueue *queue, ClapperQueueProgressionMode mode);
ClapperQueueProgressionMode clapper_queue_get_progression_mode (ClapperQueue *queue);
void clapper_queue_set_gapless (ClapperQueue *queue, gboolean gapless);
gboolean clapper_queue_get_gapless (ClapperQueue *queue);
void clapper_queue_set_instant (ClapperQueue *queue, gboolean instant);
gboolean clapper_queue_get_instant (ClapperQueue *queue);
G_END_DECLS

View File

@@ -0,0 +1,37 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include "clapper-stream-list.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
ClapperStreamList * clapper_stream_list_new (void);
G_GNUC_INTERNAL
void clapper_stream_list_replace_streams (ClapperStreamList *list, GList *streams);
G_GNUC_INTERNAL
ClapperStream * clapper_stream_list_get_stream_for_gst_stream (ClapperStreamList *list, GstStream *gst_stream);
G_END_DECLS

View File

@@ -0,0 +1,546 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperStreamList:
*
* A list of media streams.
*/
#include <gio/gio.h>
#include "clapper-stream-list-private.h"
#include "clapper-stream-private.h"
#include "clapper-player-private.h"
#include "clapper-playbin-bus-private.h"
#define GST_CAT_DEFAULT clapper_stream_list_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperStreamList
{
GstObject parent;
GPtrArray *streams;
ClapperStream *current_stream;
guint current_index;
gboolean in_refresh;
};
enum
{
PROP_0,
PROP_CURRENT_STREAM,
PROP_CURRENT_INDEX,
PROP_N_STREAMS,
PROP_LAST
};
static void clapper_stream_list_model_iface_init (GListModelInterface *iface);
#define parent_class clapper_stream_list_parent_class
G_DEFINE_TYPE_WITH_CODE (ClapperStreamList, clapper_stream_list, GST_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, clapper_stream_list_model_iface_init));
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static GType
clapper_stream_list_model_get_item_type (GListModel *model)
{
return CLAPPER_TYPE_STREAM;
}
static guint
clapper_stream_list_model_get_n_items (GListModel *model)
{
ClapperStreamList *self = CLAPPER_STREAM_LIST_CAST (model);
guint n_streams;
GST_OBJECT_LOCK (self);
n_streams = self->streams->len;
GST_OBJECT_UNLOCK (self);
return n_streams;
}
static gpointer
clapper_stream_list_model_get_item (GListModel *model, guint index)
{
ClapperStreamList *self = CLAPPER_STREAM_LIST_CAST (model);
ClapperStream *stream = NULL;
GST_OBJECT_LOCK (self);
if (G_LIKELY (index < self->streams->len))
stream = gst_object_ref (g_ptr_array_index (self->streams, index));
GST_OBJECT_UNLOCK (self);
return stream;
}
static void
clapper_stream_list_model_iface_init (GListModelInterface *iface)
{
iface->get_item_type = clapper_stream_list_model_get_item_type;
iface->get_n_items = clapper_stream_list_model_get_n_items;
iface->get_item = clapper_stream_list_model_get_item;
}
static void
_post_stream_change (ClapperStreamList *self)
{
ClapperPlayer *player;
GST_OBJECT_LOCK (self);
/* We will do a single initial selection ourselves
* after all lists are refreshed, so do nothing here yet */
if (G_UNLIKELY (self->in_refresh)) {
GST_WARNING_OBJECT (self, "Trying to select/autoselect stream before"
" initial selection. This is not supported, please fix your app.");
GST_OBJECT_UNLOCK (self);
return;
}
GST_OBJECT_UNLOCK (self);
player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
if (G_LIKELY (player != NULL)) {
clapper_playbin_bus_post_stream_change (player->bus);
gst_object_unref (player);
}
}
static void
_announce_current_stream_and_index_change (ClapperStreamList *self)
{
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
gboolean is_main_thread;
if (G_UNLIKELY (player == NULL))
return;
is_main_thread = g_main_context_is_owner (g_main_context_default ());
GST_DEBUG_OBJECT (self, "Announcing current stream change from %smain thread,"
" now: %" GST_PTR_FORMAT " (index: %u)",
(is_main_thread) ? "" : "non-", self->current_stream, self->current_index);
if (is_main_thread) {
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_STREAM]);
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_INDEX]);
} else {
clapper_app_bus_post_prop_notify (player->app_bus,
GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_STREAM]);
clapper_app_bus_post_prop_notify (player->app_bus,
GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_INDEX]);
}
gst_object_unref (player);
}
static gboolean
clapper_stream_list_select_index_unlocked (ClapperStreamList *self, guint index)
{
ClapperStream *stream = NULL;
if (index != CLAPPER_STREAM_LIST_INVALID_POSITION)
stream = g_ptr_array_index (self->streams, index);
if (gst_object_replace ((GstObject **) &self->current_stream, GST_OBJECT_CAST (stream))) {
self->current_index = index;
return TRUE;
}
return FALSE;
}
/*
* clapper_stream_list_new:
*
* Returns: (transfer full): a new #ClapperStreamList instance
*/
ClapperStreamList *
clapper_stream_list_new (void)
{
ClapperStreamList *list;
list = g_object_new (CLAPPER_TYPE_STREAM_LIST, NULL);
gst_object_ref_sink (list);
return list;
}
/**
* clapper_stream_list_select_stream:
* @list: a #ClapperStreamList
* @stream: a #ClapperStream
*
* Selects #ClapperStream from @list to be activated.
*
* Returns: %TRUE if stream was in the @list, %FALSE otherwise.
*/
gboolean
clapper_stream_list_select_stream (ClapperStreamList *self, ClapperStream *stream)
{
gboolean found, changed = FALSE;
guint index = 0;
g_return_val_if_fail (CLAPPER_IS_STREAM_LIST (self), FALSE);
g_return_val_if_fail (CLAPPER_IS_STREAM (stream), FALSE);
GST_OBJECT_LOCK (self);
if ((found = g_ptr_array_find (self->streams, stream, &index)))
changed = clapper_stream_list_select_index_unlocked (self, index);
GST_OBJECT_UNLOCK (self);
if (changed) {
_post_stream_change (self);
_announce_current_stream_and_index_change (self);
}
return found;
}
/**
* clapper_stream_list_select_index:
* @list: a #ClapperStreamList
* @index: a stream index
*
* Selects #ClapperStream at @index from @list as current one.
*
* Returns: %TRUE if stream could be selected, %FALSE otherwise.
*/
gboolean
clapper_stream_list_select_index (ClapperStreamList *self, guint index)
{
gboolean found, changed = FALSE;
g_return_val_if_fail (CLAPPER_IS_STREAM_LIST (self), FALSE);
g_return_val_if_fail (index != CLAPPER_STREAM_LIST_INVALID_POSITION, FALSE);
GST_OBJECT_LOCK (self);
if ((found = index < self->streams->len))
changed = clapper_stream_list_select_index_unlocked (self, index);
GST_OBJECT_UNLOCK (self);
if (changed) {
_post_stream_change (self);
_announce_current_stream_and_index_change (self);
}
return found;
}
/**
* clapper_stream_list_get_stream:
* @list: a #ClapperStreamList
* @index: a stream index
*
* Get the #ClapperStream at index.
*
* This behaves the same as [method@Gio.ListModel.get_item], and is here
* for code uniformity and convenience to avoid type casting by user.
*
* Returns: (transfer full) (nullable): The #ClapperStream at @index.
*/
ClapperStream *
clapper_stream_list_get_stream (ClapperStreamList *self, guint index)
{
g_return_val_if_fail (CLAPPER_IS_STREAM_LIST (self), NULL);
return g_list_model_get_item (G_LIST_MODEL (self), index);
}
/**
* clapper_stream_list_get_current_stream:
* @list: a #ClapperStreamList
*
* Get the currently selected #ClapperStream.
*
* Returns: (transfer full) (nullable): The current #ClapperStream.
*/
ClapperStream *
clapper_stream_list_get_current_stream (ClapperStreamList *self)
{
ClapperStream *stream = NULL;
g_return_val_if_fail (CLAPPER_IS_STREAM_LIST (self), NULL);
GST_OBJECT_LOCK (self);
if (self->current_stream)
stream = gst_object_ref (self->current_stream);
GST_OBJECT_UNLOCK (self);
return stream;
}
/**
* clapper_stream_list_get_current_index:
* @list: a #ClapperStreamList
*
* Get index of the currently selected #ClapperStream.
*
* Returns: Current stream index or [const@Clapper.STREAM_LIST_INVALID_POSITION]
* when nothing is selected.
*/
guint
clapper_stream_list_get_current_index (ClapperStreamList *self)
{
guint index;
g_return_val_if_fail (CLAPPER_IS_STREAM_LIST (self), CLAPPER_STREAM_LIST_INVALID_POSITION);
GST_OBJECT_LOCK (self);
index = self->current_index;
GST_OBJECT_UNLOCK (self);
return index;
}
/**
* clapper_stream_list_get_n_streams:
* @list: a #ClapperStreamList
*
* Get the number of streams in #ClapperStreamList.
*
* This behaves the same as g_list_model_get_n_items(), and is here
* for code uniformity and convenience to avoid type casting by user.
*
* Returns: The number of streams in #ClapperStreamList.
*/
guint
clapper_stream_list_get_n_streams (ClapperStreamList *self)
{
g_return_val_if_fail (CLAPPER_IS_STREAM_LIST (self), 0);
return g_list_model_get_n_items (G_LIST_MODEL (self));
}
void
clapper_stream_list_replace_streams (ClapperStreamList *self, GList *streams)
{
GList *st;
guint prev_n_streams, n_streams;
guint index = 0, selected_index = 0;
gboolean changed, selected = FALSE;
GST_OBJECT_LOCK (self);
self->in_refresh = TRUE;
prev_n_streams = self->streams->len;
if (prev_n_streams > 0)
g_ptr_array_remove_range (self->streams, 0, prev_n_streams);
for (st = streams; st != NULL; st = st->next) {
ClapperStream *stream = CLAPPER_STREAM_CAST (st->data);
/* Try to select first "default" stream, while avoiding
* streams that should not be selected by default.
* NOTE: This works only with playbin3 */
if (!selected) {
GstStream *gst_stream = clapper_stream_get_gst_stream (stream);
GstStreamFlags flags = gst_stream_get_stream_flags (gst_stream);
GST_LOG_OBJECT (self, "Stream flags: %i", flags);
if ((flags & GST_STREAM_FLAG_SELECT) == GST_STREAM_FLAG_SELECT) {
GST_DEBUG_OBJECT (self, "Stream has \"select\" stream flag");
selected = TRUE;
selected_index = index;
} else if ((flags & GST_STREAM_FLAG_UNSELECT) == GST_STREAM_FLAG_UNSELECT) {
GST_DEBUG_OBJECT (self, "Stream has \"unselect\" stream flag");
if (selected_index == index)
selected_index++;
}
}
g_ptr_array_add (self->streams, stream);
gst_object_set_parent (GST_OBJECT_CAST (stream), GST_OBJECT_CAST (self));
index++;
}
n_streams = self->streams->len;
GST_OBJECT_UNLOCK (self);
if (prev_n_streams > 0 || n_streams > 0) {
g_list_model_items_changed (G_LIST_MODEL (self), 0, prev_n_streams, n_streams);
if (prev_n_streams != n_streams)
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_N_STREAMS]);
}
/* This can happen when ALL streams had "unselect" flag.
* In this case just select the first one. */
if (n_streams > 0) {
if (G_UNLIKELY (selected_index > n_streams - 1))
selected_index = 0;
} else {
selected_index = CLAPPER_STREAM_LIST_INVALID_POSITION;
}
/* TODO: Consider adding an API (or signal that returns index)
* to select preferred initial stream (by e.g. language) */
GST_OBJECT_LOCK (self);
changed = clapper_stream_list_select_index_unlocked (self, selected_index);
GST_OBJECT_UNLOCK (self);
if (changed) {
GST_INFO_OBJECT (self, "Initially selecting stream index: %u", selected_index);
_announce_current_stream_and_index_change (self);
}
GST_OBJECT_LOCK (self);
self->in_refresh = FALSE;
GST_OBJECT_UNLOCK (self);
}
ClapperStream *
clapper_stream_list_get_stream_for_gst_stream (ClapperStreamList *self, GstStream *gst_stream)
{
ClapperStream *found_stream = NULL;
guint i;
GST_OBJECT_LOCK (self);
for (i = 0; i < self->streams->len; ++i) {
ClapperStream *stream = g_ptr_array_index (self->streams, i);
GstStream *list_gst_stream = clapper_stream_get_gst_stream (stream);
if (gst_stream == list_gst_stream) {
found_stream = gst_object_ref (stream);
break;
}
}
GST_OBJECT_UNLOCK (self);
return found_stream;
}
static void
_stream_remove_func (ClapperStream *stream)
{
gst_object_unparent (GST_OBJECT_CAST (stream));
gst_object_unref (stream);
}
static void
clapper_stream_list_init (ClapperStreamList *self)
{
self->streams = g_ptr_array_new_with_free_func ((GDestroyNotify) _stream_remove_func);
self->current_index = CLAPPER_STREAM_LIST_INVALID_POSITION;
}
static void
clapper_stream_list_finalize (GObject *object)
{
ClapperStreamList *self = CLAPPER_STREAM_LIST_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
gst_clear_object (&self->current_stream);
g_ptr_array_unref (self->streams);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_stream_list_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperStreamList *self = CLAPPER_STREAM_LIST_CAST (object);
switch (prop_id) {
case PROP_CURRENT_STREAM:
g_value_take_object (value, clapper_stream_list_get_current_stream (self));
break;
case PROP_CURRENT_INDEX:
g_value_set_uint (value, clapper_stream_list_get_current_index (self));
break;
case PROP_N_STREAMS:
g_value_set_uint (value, clapper_stream_list_get_n_streams (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_stream_list_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
ClapperStreamList *self = CLAPPER_STREAM_LIST_CAST (object);
switch (prop_id) {
case PROP_CURRENT_INDEX:
clapper_stream_list_select_index (self, g_value_get_uint (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_stream_list_class_init (ClapperStreamListClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperstreamlist", 0,
"Clapper Stream List");
gobject_class->get_property = clapper_stream_list_get_property;
gobject_class->set_property = clapper_stream_list_set_property;
gobject_class->finalize = clapper_stream_list_finalize;
/**
* ClapperStreamList:current-stream:
*
* Currently selected stream.
*/
param_specs[PROP_CURRENT_STREAM] = g_param_spec_object ("current-stream",
NULL, NULL, CLAPPER_TYPE_STREAM,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperStreamList:current-index:
*
* Index of currently selected stream.
*/
param_specs[PROP_CURRENT_INDEX] = g_param_spec_uint ("current-index",
NULL, NULL, 0, G_MAXUINT, CLAPPER_STREAM_LIST_INVALID_POSITION,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperStreamList:n-streams:
*
* Number of streams in the list.
*/
param_specs[PROP_N_STREAMS] = g_param_spec_uint ("n-streams",
NULL, NULL, 0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
}

View File

@@ -0,0 +1,58 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <clapper/clapper-stream.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_STREAM_LIST (clapper_stream_list_get_type())
#define CLAPPER_STREAM_LIST_CAST(obj) ((ClapperStreamList *)(obj))
G_DECLARE_FINAL_TYPE (ClapperStreamList, clapper_stream_list, CLAPPER, STREAM_LIST, GstObject)
/**
* CLAPPER_STREAM_LIST_INVALID_POSITION:
*
* The value used to refer to an invalid position in a #ClapperStreamList
*/
#define CLAPPER_STREAM_LIST_INVALID_POSITION ((guint) 0xffffffff)
gboolean clapper_stream_list_select_stream (ClapperStreamList *list, ClapperStream *stream);
gboolean clapper_stream_list_select_index (ClapperStreamList *list, guint index);
ClapperStream * clapper_stream_list_get_stream (ClapperStreamList *list, guint index);
ClapperStream * clapper_stream_list_get_current_stream (ClapperStreamList *list);
guint clapper_stream_list_get_current_index (ClapperStreamList *list);
guint clapper_stream_list_get_n_streams (ClapperStreamList *list);
G_END_DECLS

View File

@@ -0,0 +1,53 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gst/gst.h>
#include "clapper-stream.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
ClapperStream * clapper_stream_new (GstStream *gst_stream);
G_GNUC_INTERNAL
void clapper_stream_set_gst_stream (ClapperStream *stream, GstStream *gst_stream);
G_GNUC_INTERNAL
GstStream * clapper_stream_get_gst_stream (ClapperStream *stream);
G_GNUC_INTERNAL
void clapper_stream_take_string_prop (ClapperStream *stream, GParamSpec *pspec, gchar **ptr, gchar *value);
G_GNUC_INTERNAL
void clapper_stream_set_string_prop (ClapperStream *stream, GParamSpec *pspec, gchar **ptr, const gchar *value);
G_GNUC_INTERNAL
void clapper_stream_set_int_prop (ClapperStream *stream, GParamSpec *pspec, gint *ptr, gint value);
G_GNUC_INTERNAL
void clapper_stream_set_uint_prop (ClapperStream *stream, GParamSpec *pspec, guint *ptr, guint value);
G_GNUC_INTERNAL
void clapper_stream_set_double_prop (ClapperStream *stream, GParamSpec *pspec, gdouble *ptr, gdouble value);
G_END_DECLS

View File

@@ -0,0 +1,377 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperStream:
*
* Represents a stream within media.
*/
#include <gst/tag/tag.h>
#include "clapper-stream-private.h"
#include "clapper-player-private.h"
#define GST_CAT_DEFAULT clapper_stream_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
typedef struct _ClapperStreamPrivate ClapperStreamPrivate;
struct _ClapperStreamPrivate
{
GstStream *gst_stream;
ClapperStreamType stream_type;
gchar *title;
};
enum
{
PROP_0,
PROP_STREAM_TYPE,
PROP_TITLE,
PROP_LAST
};
#define parent_class clapper_stream_parent_class
G_DEFINE_TYPE_WITH_PRIVATE (ClapperStream, clapper_stream, GST_TYPE_OBJECT);
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static void
_update_using_tags (ClapperStream *self, GstTagList *tags)
{
ClapperStreamPrivate *priv = clapper_stream_get_instance_private (self);
gchar *title = NULL;
gst_tag_list_get_string_index (tags, GST_TAG_TITLE, 0, &title);
clapper_stream_take_string_prop (self, param_specs[PROP_TITLE], &priv->title, title);
}
ClapperStream *
clapper_stream_new (GstStream *gst_stream)
{
ClapperStream *stream;
ClapperStreamPrivate *priv;
stream = g_object_new (CLAPPER_TYPE_STREAM, NULL);
gst_object_ref_sink (stream);
priv = clapper_stream_get_instance_private (stream);
priv->gst_stream = gst_object_ref (gst_stream);
return stream;
}
/*
* This should be called only during stream construction
*/
void
clapper_stream_set_gst_stream (ClapperStream *stream, GstStream *gst_stream)
{
ClapperStreamPrivate *priv = clapper_stream_get_instance_private (stream);
if (G_LIKELY (gst_object_replace ((GstObject **) &priv->gst_stream,
GST_OBJECT_CAST (gst_stream)))) {
GstCaps *caps = gst_stream_get_caps (gst_stream);
GstTagList *tags = gst_stream_get_tags (gst_stream);
if (caps || tags) {
ClapperStreamClass *stream_class = CLAPPER_STREAM_GET_CLASS (stream);
stream_class->internal_stream_updated (stream, caps, tags);
gst_clear_caps (&caps);
gst_clear_tag_list (&tags);
}
}
}
/**
* clapper_stream_get_stream_type:
* @stream: a #ClapperStream
*
* Get the #ClapperStreamType of @stream.
*
* Returns: type of stream.
*/
ClapperStreamType
clapper_stream_get_stream_type (ClapperStream *self)
{
ClapperStreamPrivate *priv;
g_return_val_if_fail (CLAPPER_IS_STREAM (self), CLAPPER_STREAM_TYPE_UNKNOWN);
priv = clapper_stream_get_instance_private (self);
return priv->stream_type;
}
/**
* clapper_stream_get_title:
* @stream: a #ClapperStream
*
* Get the title of @stream, if any.
*
* Returns: (transfer full) (nullable): title of stream.
*/
gchar *
clapper_stream_get_title (ClapperStream *self)
{
ClapperStreamPrivate *priv;
gchar *title;
g_return_val_if_fail (CLAPPER_IS_STREAM (self), NULL);
priv = clapper_stream_get_instance_private (self);
GST_OBJECT_LOCK (self);
title = g_strdup (priv->title);
GST_OBJECT_UNLOCK (self);
return title;
}
GstStream *
clapper_stream_get_gst_stream (ClapperStream *self)
{
ClapperStreamPrivate *priv = clapper_stream_get_instance_private (self);
return priv->gst_stream;
}
static void
clapper_stream_prop_notify (ClapperStream *self, GParamSpec *pspec)
{
ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self));
/* NOTE: This happens when props are initially set during construction
* (before parented) which is fine, since we do not have to notify
* when user does not have access to object yet. */
if (!player)
return;
clapper_app_bus_post_prop_notify (player->app_bus,
GST_OBJECT_CAST (self), pspec);
gst_object_unref (player);
}
void
clapper_stream_take_string_prop (ClapperStream *self,
GParamSpec *pspec, gchar **ptr, gchar *value)
{
gboolean changed;
GST_OBJECT_LOCK (self);
if ((changed = g_strcmp0 (*ptr, value) != 0)) {
g_free (*ptr);
*ptr = value;
GST_DEBUG_OBJECT (self, "Set %s: %s",
g_param_spec_get_name (pspec), value);
}
GST_OBJECT_UNLOCK (self);
if (changed)
clapper_stream_prop_notify (self, pspec);
else
g_free (value);
}
void
clapper_stream_set_string_prop (ClapperStream *self,
GParamSpec *pspec, gchar **ptr, const gchar *value)
{
gboolean changed;
GST_OBJECT_LOCK (self);
if ((changed = g_set_str (ptr, value))) {
GST_DEBUG_OBJECT (self, "Set %s: %s",
g_param_spec_get_name (pspec), value);
}
GST_OBJECT_UNLOCK (self);
if (changed)
clapper_stream_prop_notify (self, pspec);
}
void
clapper_stream_set_int_prop (ClapperStream *self,
GParamSpec *pspec, gint *ptr, gint value)
{
gboolean changed;
GST_OBJECT_LOCK (self);
if ((changed = *ptr != value)) {
*ptr = value;
GST_DEBUG_OBJECT (self, "Set %s: %i",
g_param_spec_get_name (pspec), value);
}
GST_OBJECT_UNLOCK (self);
if (changed)
clapper_stream_prop_notify (self, pspec);
}
void
clapper_stream_set_uint_prop (ClapperStream *self,
GParamSpec *pspec, guint *ptr, guint value)
{
gboolean changed;
GST_OBJECT_LOCK (self);
if ((changed = *ptr != value)) {
*ptr = value;
GST_DEBUG_OBJECT (self, "Set %s: %u",
g_param_spec_get_name (pspec), value);
}
GST_OBJECT_UNLOCK (self);
if (changed)
clapper_stream_prop_notify (self, pspec);
}
void
clapper_stream_set_double_prop (ClapperStream *self,
GParamSpec *pspec, gdouble *ptr, gdouble value)
{
gboolean changed;
GST_OBJECT_LOCK (self);
if ((changed = !G_APPROX_VALUE (*ptr, value, FLT_EPSILON))) {
*ptr = value;
GST_DEBUG_OBJECT (self, "Set %s: %lf",
g_param_spec_get_name (pspec), value);
}
GST_OBJECT_UNLOCK (self);
if (changed)
clapper_stream_prop_notify (self, pspec);
}
static void
clapper_stream_init (ClapperStream *self)
{
ClapperStreamPrivate *priv = clapper_stream_get_instance_private (self);
priv->stream_type = CLAPPER_STREAM_TYPE_UNKNOWN;
}
static void
clapper_stream_finalize (GObject *object)
{
ClapperStream *self = CLAPPER_STREAM_CAST (object);
ClapperStreamPrivate *priv = clapper_stream_get_instance_private (self);
GST_TRACE_OBJECT (self, "Finalize");
gst_clear_object (&priv->gst_stream);
g_free (priv->title);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_stream_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperStream *self = CLAPPER_STREAM_CAST (object);
switch (prop_id) {
case PROP_STREAM_TYPE:
g_value_set_enum (value, clapper_stream_get_stream_type (self));
break;
case PROP_TITLE:
g_value_take_string (value, clapper_stream_get_title (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_stream_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
ClapperStream *self = CLAPPER_STREAM_CAST (object);
ClapperStreamPrivate *priv = clapper_stream_get_instance_private (self);
switch (prop_id) {
case PROP_STREAM_TYPE:
priv->stream_type = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_stream_internal_stream_updated (ClapperStream *self,
GstCaps *caps, GstTagList *tags)
{
if (caps)
GST_LOG_OBJECT (self, "Caps: %" GST_PTR_FORMAT, caps);
if (tags) {
GST_LOG_OBJECT (self, "Tags: %" GST_PTR_FORMAT, tags);
_update_using_tags (self, tags);
}
}
static void
clapper_stream_class_init (ClapperStreamClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
ClapperStreamClass *stream_class = (ClapperStreamClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperstream", 0,
"Clapper Stream");
gobject_class->get_property = clapper_stream_get_property;
gobject_class->set_property = clapper_stream_set_property;
gobject_class->finalize = clapper_stream_finalize;
stream_class->internal_stream_updated = clapper_stream_internal_stream_updated;
/**
* ClapperStream:stream-type:
*
* Type of stream.
*/
param_specs[PROP_STREAM_TYPE] = g_param_spec_enum ("stream-type",
NULL, NULL, CLAPPER_TYPE_STREAM_TYPE, CLAPPER_STREAM_TYPE_UNKNOWN,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperStream:title:
*
* Title of stream.
*/
param_specs[PROP_TITLE] = g_param_spec_string ("title",
NULL, NULL, NULL,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
}

View File

@@ -0,0 +1,65 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <clapper/clapper-enums.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_STREAM (clapper_stream_get_type())
#define CLAPPER_STREAM_CAST(obj) ((ClapperStream *)(obj))
G_DECLARE_DERIVABLE_TYPE (ClapperStream, clapper_stream, CLAPPER, STREAM, GstObject)
struct _ClapperStreamClass
{
GstObjectClass parent_class;
/**
* ClapperStreamClass::internal_stream_updated:
* @stream: a #ClapperStream
* @caps: (nullable): an updated #GstCaps if changed
* @tags: (nullable): an updated #GstTagList if changed
*
* This function is called when internal #GstStream gets updated.
* Meant for internal usage only. Used for subclasses to update
* their properties accordingly.
*
* Note that this vfunc is called from different threads.
*/
void (* internal_stream_updated) (ClapperStream *stream, GstCaps *caps, GstTagList *tags);
/*< private >*/
gpointer padding[4];
};
ClapperStreamType clapper_stream_get_stream_type (ClapperStream *stream);
gchar * clapper_stream_get_title (ClapperStream *stream);
G_END_DECLS

View File

@@ -0,0 +1,32 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gst/gst.h>
#include "clapper-subtitle-stream.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
ClapperStream * clapper_subtitle_stream_new (GstStream *gst_stream);
G_END_DECLS

View File

@@ -0,0 +1,214 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperSubtitleStream:
*
* Represents a subtitle stream within media.
*/
#include <gst/tag/tag.h>
#include "clapper-subtitle-stream-private.h"
#include "clapper-stream-private.h"
#define GST_CAT_DEFAULT clapper_subtitle_stream_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperSubtitleStream
{
ClapperStream parent;
gchar *lang_code;
gchar *lang_name;
};
#define parent_class clapper_subtitle_stream_parent_class
G_DEFINE_TYPE (ClapperSubtitleStream, clapper_subtitle_stream, CLAPPER_TYPE_STREAM);
enum
{
PROP_0,
PROP_LANG_CODE,
PROP_LANG_NAME,
PROP_LAST
};
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static void
_update_using_tags (ClapperSubtitleStream *self, GstTagList *tags)
{
ClapperStream *stream = CLAPPER_STREAM_CAST (self);
gchar *lang_code = NULL, *lang_name = NULL;
/* Prefer code (and name from it), fallback to extracted name */
if (!gst_tag_list_get_string_index (tags, GST_TAG_LANGUAGE_CODE, 0, &lang_code))
gst_tag_list_get_string_index (tags, GST_TAG_LANGUAGE_NAME, 0, &lang_name);
clapper_stream_take_string_prop (stream, param_specs[PROP_LANG_CODE], &self->lang_code, lang_code);
clapper_stream_take_string_prop (stream, param_specs[PROP_LANG_NAME], &self->lang_name, lang_name);
}
ClapperStream *
clapper_subtitle_stream_new (GstStream *gst_stream)
{
ClapperSubtitleStream *subtitle_stream;
subtitle_stream = g_object_new (CLAPPER_TYPE_SUBTITLE_STREAM,
"stream-type", CLAPPER_STREAM_TYPE_SUBTITLE, NULL);
gst_object_ref_sink (subtitle_stream);
clapper_stream_set_gst_stream (CLAPPER_STREAM_CAST (subtitle_stream), gst_stream);
return CLAPPER_STREAM_CAST (subtitle_stream);
}
/**
* clapper_subtitle_stream_get_lang_code:
* @stream: a #ClapperSubtitleStream
*
* Get an ISO-639 language code of the @stream.
*
* Returns: (transfer full) (nullable): the language code of subtitle stream.
*/
gchar *
clapper_subtitle_stream_get_lang_code (ClapperSubtitleStream *self)
{
gchar *lang_code;
g_return_val_if_fail (CLAPPER_IS_SUBTITLE_STREAM (self), NULL);
GST_OBJECT_LOCK (self);
lang_code = g_strdup (self->lang_code);
GST_OBJECT_UNLOCK (self);
return lang_code;
}
/**
* clapper_subtitle_stream_get_lang_name:
* @stream: a #ClapperSubtitleStream
*
* Get an ISO-639 language code of the @stream.
*
* Returns: (transfer full) (nullable): the language code of subtitle stream.
*/
gchar *
clapper_subtitle_stream_get_lang_name (ClapperSubtitleStream *self)
{
gchar *lang_name = NULL;
g_return_val_if_fail (CLAPPER_IS_SUBTITLE_STREAM (self), NULL);
GST_OBJECT_LOCK (self);
/* Prefer from code as its translated to user locale,
* otherwise try to fallback to the one sent in tags */
if (self->lang_code)
lang_name = g_strdup (gst_tag_get_language_name (self->lang_code));
if (!lang_name)
lang_name = g_strdup (self->lang_name);
GST_OBJECT_UNLOCK (self);
return lang_name;
}
static void
clapper_subtitle_stream_init (ClapperSubtitleStream *self)
{
}
static void
clapper_subtitle_stream_finalize (GObject *object)
{
ClapperSubtitleStream *self = CLAPPER_SUBTITLE_STREAM_CAST (object);
g_free (self->lang_code);
g_free (self->lang_name);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_subtitle_stream_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperSubtitleStream *self = CLAPPER_SUBTITLE_STREAM_CAST (object);
switch (prop_id) {
case PROP_LANG_CODE:
g_value_take_string (value, clapper_subtitle_stream_get_lang_code (self));
break;
case PROP_LANG_NAME:
g_value_take_string (value, clapper_subtitle_stream_get_lang_name (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_subtitle_stream_internal_stream_updated (ClapperStream *stream,
GstCaps *caps, GstTagList *tags)
{
ClapperSubtitleStream *self = CLAPPER_SUBTITLE_STREAM_CAST (stream);
CLAPPER_STREAM_CLASS (parent_class)->internal_stream_updated (stream, caps, tags);
if (tags)
_update_using_tags (self, tags);
}
static void
clapper_subtitle_stream_class_init (ClapperSubtitleStreamClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
ClapperStreamClass *stream_class = (ClapperStreamClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappersubtitlestream", 0,
"Clapper Subtitle Stream");
gobject_class->get_property = clapper_subtitle_stream_get_property;
gobject_class->finalize = clapper_subtitle_stream_finalize;
stream_class->internal_stream_updated = clapper_subtitle_stream_internal_stream_updated;
/**
* ClapperSubtitleStream:lang-code:
*
* Stream language code in ISO-639 format.
*/
param_specs[PROP_LANG_CODE] = g_param_spec_string ("lang-code",
NULL, NULL, NULL,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperSubtitleStream:lang-name:
*
* Stream full language name determined from lang code.
*/
param_specs[PROP_LANG_NAME] = g_param_spec_string ("lang-name",
NULL, NULL, NULL,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
}

View File

@@ -0,0 +1,41 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <clapper/clapper-stream.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_SUBTITLE_STREAM (clapper_subtitle_stream_get_type())
#define CLAPPER_SUBTITLE_STREAM_CAST(obj) ((ClapperSubtitleStream *)(obj))
G_DECLARE_FINAL_TYPE (ClapperSubtitleStream, clapper_subtitle_stream, CLAPPER, SUBTITLE_STREAM, ClapperStream)
gchar * clapper_subtitle_stream_get_lang_code (ClapperSubtitleStream *stream);
gchar * clapper_subtitle_stream_get_lang_name (ClapperSubtitleStream *stream);
G_END_DECLS

View File

@@ -0,0 +1,193 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperThreadedObject:
*
* A base class for creating objects that work within a separate thread.
*/
#include "clapper-threaded-object.h"
#define GST_CAT_DEFAULT clapper_threaded_object_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
typedef struct _ClapperThreadedObjectPrivate ClapperThreadedObjectPrivate;
struct _ClapperThreadedObjectPrivate
{
GMutex lock; // Separate mutex to not deadlock with subclass on wakeups
GCond cond;
GThread *thread;
GMainContext *context;
GMainLoop *loop;
gboolean started;
};
#define parent_class clapper_threaded_object_parent_class
G_DEFINE_TYPE_WITH_PRIVATE (ClapperThreadedObject, clapper_threaded_object, GST_TYPE_OBJECT)
/**
* clapper_threaded_object_get_context:
* @threaded_object: a #ClapperThreadedObject
*
* Get the #GMainContext of the thread used by this object.
*
* Useful when you want to invoke object thread to do some
* action in it from a different thread.
*
* Returns: a #GMainContext of the object used thread.
*/
GMainContext *
clapper_threaded_object_get_context (ClapperThreadedObject *self)
{
ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self);
return priv->context;
}
static gboolean
main_loop_running_cb (ClapperThreadedObject *self)
{
ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self);
GST_TRACE_OBJECT (self, "Main loop running now");
g_mutex_lock (&priv->lock);
priv->started = TRUE;
g_cond_signal (&priv->cond);
g_mutex_unlock (&priv->lock);
return G_SOURCE_REMOVE;
}
static gpointer
clapper_threaded_object_main (ClapperThreadedObject *self)
{
ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self);
ClapperThreadedObjectClass *threaded_object_class = CLAPPER_THREADED_OBJECT_GET_CLASS (self);
const gchar *obj_cls_name = G_OBJECT_CLASS_NAME (threaded_object_class);
GSource *idle_source;
GST_TRACE_OBJECT (self, "%s thread: %p", obj_cls_name, g_thread_self ());
priv->context = g_main_context_new ();
priv->loop = g_main_loop_new (priv->context, FALSE);
g_main_context_push_thread_default (priv->context);
if (threaded_object_class->thread_start)
threaded_object_class->thread_start (self);
idle_source = g_idle_source_new ();
g_source_set_callback (idle_source,
(GSourceFunc) main_loop_running_cb, self, NULL);
g_source_attach (idle_source, priv->context);
g_source_unref (idle_source);
GST_DEBUG_OBJECT (self, "%s main loop running", obj_cls_name);
g_main_loop_run (priv->loop);
GST_DEBUG_OBJECT (self, "%s main loop stopped", obj_cls_name);
if (threaded_object_class->thread_stop)
threaded_object_class->thread_stop (self);
g_main_context_pop_thread_default (priv->context);
g_main_context_unref (priv->context);
return NULL;
}
static void
clapper_threaded_object_init (ClapperThreadedObject *self)
{
ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self);
g_mutex_init (&priv->lock);
g_cond_init (&priv->cond);
}
static void
clapper_threaded_object_constructed (GObject *object)
{
ClapperThreadedObject *self = CLAPPER_THREADED_OBJECT_CAST (object);
ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self);
GST_TRACE_OBJECT (self, "Constructed from thread: %p", g_thread_self ());
g_mutex_lock (&priv->lock);
priv->thread = g_thread_new (GST_OBJECT_NAME (object),
(GThreadFunc) clapper_threaded_object_main, self);
while (!priv->started)
g_cond_wait (&priv->cond, &priv->lock);
g_mutex_unlock (&priv->lock);
G_OBJECT_CLASS (parent_class)->constructed (object);
}
static void
clapper_threaded_object_dispose (GObject *object)
{
ClapperThreadedObject *self = CLAPPER_THREADED_OBJECT_CAST (object);
ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self);
g_mutex_lock (&priv->lock);
if (priv->loop) {
g_main_loop_quit (priv->loop);
if (G_LIKELY (priv->thread != g_thread_self ()))
g_thread_join (priv->thread);
else
g_thread_unref (priv->thread);
g_clear_pointer (&priv->loop, g_main_loop_unref);
}
g_mutex_unlock (&priv->lock);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
clapper_threaded_object_finalize (GObject *object)
{
ClapperThreadedObject *self = CLAPPER_THREADED_OBJECT_CAST (object);
ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self);
g_mutex_clear (&priv->lock);
g_cond_clear (&priv->cond);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_threaded_object_class_init (ClapperThreadedObjectClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperthreadedobject", 0,
"Clapper Threaded Object");
gobject_class->constructed = clapper_threaded_object_constructed;
gobject_class->dispose = clapper_threaded_object_dispose;
gobject_class->finalize = clapper_threaded_object_finalize;
}

View File

@@ -0,0 +1,73 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_THREADED_OBJECT (clapper_threaded_object_get_type())
#define CLAPPER_THREADED_OBJECT_CAST(obj) ((ClapperThreadedObject *)(obj))
G_DECLARE_DERIVABLE_TYPE (ClapperThreadedObject, clapper_threaded_object, CLAPPER, THREADED_OBJECT, GstObject)
/**
* ClapperThreadedObjectClass:
* @parent_class: The object class structure.
* @thread_start: Called right after thread started.
* @thread_stop: Called when thread is going to stop.
*/
struct _ClapperThreadedObjectClass
{
GstObjectClass parent_class;
/**
* ClapperThreadedObjectClass::thread_start:
* @threaded_object: a #ClapperThreadedObject
*
* Called right after thread started.
*
* Useful for initializing objects that work within this new thread.
*/
void (* thread_start) (ClapperThreadedObject *threaded_object);
/**
* ClapperThreadedObjectClass::thread_stop:
* @threaded_object: a #ClapperThreadedObject
*
* Called when thread is going to stop.
*
* Useful for cleanup of things created on thread start.
*/
void (* thread_stop) (ClapperThreadedObject *threaded_object);
/*< private >*/
gpointer padding[4];
};
GMainContext * clapper_threaded_object_get_context (ClapperThreadedObject *threaded_object);
G_END_DECLS

View File

@@ -0,0 +1,38 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gst/gst.h>
#include "clapper-timeline.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
ClapperTimeline * clapper_timeline_new (void);
G_GNUC_INTERNAL
gboolean clapper_timeline_set_toc (ClapperTimeline *timeline, GstToc *toc, gboolean updated);
G_GNUC_INTERNAL
void clapper_timeline_refresh (ClapperTimeline *timeline);
G_END_DECLS

View File

@@ -0,0 +1,559 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperTimeline:
*
* A media timeline filled with point markers.
*/
#include <gio/gio.h>
#include "clapper-enums.h"
#include "clapper-timeline-private.h"
#include "clapper-marker-private.h"
#include "clapper-player-private.h"
#define GST_CAT_DEFAULT clapper_timeline_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperTimeline
{
GstObject parent;
GSequence *markers_seq;
GstToc *toc;
GPtrArray *pending_markers;
gboolean needs_refresh;
};
enum
{
PROP_0,
PROP_N_MARKERS,
PROP_LAST
};
static GType
clapper_timeline_list_model_get_item_type (GListModel *model)
{
return CLAPPER_TYPE_MARKER;
}
static guint
clapper_timeline_list_model_get_n_items (GListModel *model)
{
ClapperTimeline *self = CLAPPER_TIMELINE_CAST (model);
guint n_markers;
GST_OBJECT_LOCK (self);
n_markers = g_sequence_get_length (self->markers_seq);
GST_OBJECT_UNLOCK (self);
return n_markers;
}
static gpointer
clapper_timeline_list_model_get_item (GListModel *model, guint index)
{
ClapperTimeline *self = CLAPPER_TIMELINE_CAST (model);
GSequenceIter *iter;
ClapperMarker *marker = NULL;
GST_OBJECT_LOCK (self);
iter = g_sequence_get_iter_at_pos (self->markers_seq, index);
if (!g_sequence_iter_is_end (iter))
marker = gst_object_ref (g_sequence_get (iter));
GST_OBJECT_UNLOCK (self);
return marker;
}
static void
clapper_timeline_list_model_iface_init (GListModelInterface *iface)
{
iface->get_item_type = clapper_timeline_list_model_get_item_type;
iface->get_n_items = clapper_timeline_list_model_get_n_items;
iface->get_item = clapper_timeline_list_model_get_item;
}
#define parent_class clapper_timeline_parent_class
G_DEFINE_TYPE_WITH_CODE (ClapperTimeline, clapper_timeline, GST_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, clapper_timeline_list_model_iface_init));
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static void
clapper_timeline_post_item_updated (ClapperTimeline *self)
{
ClapperPlayer *player;
if ((player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)))) {
ClapperFeaturesManager *features_manager;
if ((features_manager = clapper_player_get_features_manager (player))) {
ClapperMediaItem *item;
if ((item = CLAPPER_MEDIA_ITEM (gst_object_get_parent (GST_OBJECT_CAST (self))))) {
clapper_features_manager_trigger_item_updated (features_manager, item);
gst_object_unref (item);
}
}
gst_object_unref (player);
}
}
static gint
_markers_compare_func (gconstpointer marker_a, gconstpointer marker_b,
gpointer user_data G_GNUC_UNUSED)
{
gint64 val_a, val_b, result;
/* Can happen if someone tries to insert already
* inserted marker pointer */
if (marker_a == marker_b)
return 0;
/* 1 millisecond accuracy should be enough */
val_a = clapper_marker_get_start (CLAPPER_MARKER_CAST (marker_a)) * 1000;
val_b = clapper_marker_get_start (CLAPPER_MARKER_CAST (marker_b)) * 1000;
/* If start time is the same, sort by earliest end time */
if (val_a == val_b) {
val_a = clapper_marker_get_end (CLAPPER_MARKER_CAST (marker_a)) * 1000;
val_b = clapper_marker_get_end (CLAPPER_MARKER_CAST (marker_b)) * 1000;
/* If both times are the same, check type and if they also are
* the same, we will assume that this is the same marker overall */
if (val_a == val_b) {
val_a = clapper_marker_get_marker_type (CLAPPER_MARKER_CAST (marker_a));
val_b = clapper_marker_get_marker_type (CLAPPER_MARKER_CAST (marker_b));
}
}
result = val_a - val_b;
return (result > 0) ? 1 : (result < 0) ? -1 : 0;
}
/*
* clapper_timeline_new:
*
* Returns: (transfer full): a new #ClapperTimeline instance
*/
ClapperTimeline *
clapper_timeline_new (void)
{
ClapperTimeline *timeline;
timeline = g_object_new (CLAPPER_TYPE_TIMELINE, NULL);
gst_object_ref_sink (timeline);
return timeline;
}
static inline gint
_take_marker_unlocked (ClapperTimeline *self, ClapperMarker *marker)
{
GSequenceIter *iter;
iter = g_sequence_insert_sorted (self->markers_seq, marker,
(GCompareDataFunc) _markers_compare_func, NULL);
gst_object_set_parent (GST_OBJECT_CAST (marker), GST_OBJECT_CAST (self));
return g_sequence_iter_get_position (iter);
}
/**
* clapper_timeline_insert_marker:
* @timeline: a #ClapperTimeline
* @marker: a #ClapperMarker
*
* Insert the #ClapperMarker into @timeline.
*
* Returns: %TRUE if inserted, %FALSE if marker was
* already inserted into timeline.
*/
gboolean
clapper_timeline_insert_marker (ClapperTimeline *self, ClapperMarker *marker)
{
gboolean success;
gint position = 0;
g_return_val_if_fail (CLAPPER_IS_TIMELINE (self), FALSE);
g_return_val_if_fail (CLAPPER_IS_MARKER (marker), FALSE);
GST_OBJECT_LOCK (self);
if ((success = !g_sequence_lookup (self->markers_seq, marker,
(GCompareDataFunc) _markers_compare_func, NULL)))
position = _take_marker_unlocked (self, gst_object_ref (marker));
GST_OBJECT_UNLOCK (self);
if (success) {
g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_N_MARKERS]);
clapper_timeline_post_item_updated (self);
}
return success;
}
/**
* clapper_timeline_remove_marker:
* @timeline: a #ClapperTimeline
* @marker: a #ClapperMarker
*
* Removes #ClapperMarker from the timeline.
*
* If marker was not in the @timeline, this function will do nothing,
* so it is safe to call if unsure.
*/
void
clapper_timeline_remove_marker (ClapperTimeline *self, ClapperMarker *marker)
{
GSequenceIter *iter;
gint position = 0;
gboolean success = FALSE;
g_return_if_fail (CLAPPER_IS_TIMELINE (self));
g_return_if_fail (CLAPPER_IS_MARKER (marker));
GST_OBJECT_LOCK (self);
if ((iter = g_sequence_lookup (self->markers_seq, marker,
(GCompareDataFunc) _markers_compare_func, NULL))) {
position = g_sequence_iter_get_position (iter);
g_sequence_remove (iter);
success = TRUE;
}
GST_OBJECT_UNLOCK (self);
if (success) {
g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_N_MARKERS]);
clapper_timeline_post_item_updated (self);
}
}
/**
* clapper_timeline_get_marker:
* @timeline: a #ClapperTimeline
* @index: a marker index
*
* Get the #ClapperMarker at index.
*
* This behaves the same as [method@Gio.ListModel.get_item], and is here
* for code uniformity and convenience to avoid type casting by user.
*
* Returns: (transfer full) (nullable): The #ClapperMarker at @index.
*/
ClapperMarker *
clapper_timeline_get_marker (ClapperTimeline *self, guint index)
{
g_return_val_if_fail (CLAPPER_IS_TIMELINE (self), NULL);
return g_list_model_get_item (G_LIST_MODEL (self), index);
}
/**
* clapper_timeline_get_n_markers:
* @timeline: a #ClapperTimeline
*
* Get the number of markers in #ClapperTimeline.
*
* This behaves the same as [method@Gio.ListModel.get_n_items], and is here
* for code uniformity and convenience to avoid type casting by user.
*
* Returns: The number of markers in #ClapperTimeline.
*/
guint
clapper_timeline_get_n_markers (ClapperTimeline *self)
{
g_return_val_if_fail (CLAPPER_IS_TIMELINE (self), 0);
return g_list_model_get_n_items (G_LIST_MODEL (self));
}
static void
_append_marker_from_toc_entry (ClapperTimeline *self, GstTocEntry *entry, GList **markers)
{
ClapperMarker *marker;
ClapperMarkerType marker_type;
GstTagList *tags;
gchar *title = NULL;
gint64 start = 0, stop = 0;
gdouble marker_start = 0, marker_end = CLAPPER_MARKER_NO_END;
switch (gst_toc_entry_get_entry_type (entry)) {
case GST_TOC_ENTRY_TYPE_TITLE:
marker_type = CLAPPER_MARKER_TYPE_TITLE;
break;
case GST_TOC_ENTRY_TYPE_TRACK:
marker_type = CLAPPER_MARKER_TYPE_TRACK;
break;
case GST_TOC_ENTRY_TYPE_CHAPTER:
marker_type = CLAPPER_MARKER_TYPE_CHAPTER;
break;
default:
return;
}
/* Start time is required */
if (G_UNLIKELY (!gst_toc_entry_get_start_stop_times (entry, &start, NULL)))
return;
marker_start = (gdouble) start / GST_SECOND;
if (gst_toc_entry_get_start_stop_times (entry, NULL, &stop))
marker_end = (gdouble) stop / GST_SECOND;
if ((tags = gst_toc_entry_get_tags (entry)))
gst_tag_list_get_string_index (tags, GST_TAG_TITLE, 0, &title);
marker = clapper_marker_new_internal (marker_type,
title, marker_start, marker_end);
*markers = g_list_append (*markers, marker);
g_free (title);
}
static void
_iterate_toc_entries (ClapperTimeline *self, GList *entries, GList **markers)
{
GList *en;
for (en = entries; en != NULL; en = en->next) {
GstTocEntry *entry = (GstTocEntry *) en->data;
if (gst_toc_entry_is_alternative (entry))
_iterate_toc_entries (self, gst_toc_entry_get_sub_entries (entry), markers);
else if (gst_toc_entry_is_sequence (entry))
_append_marker_from_toc_entry (self, entry, markers);
}
}
static inline void
_prepare_markers (ClapperTimeline *self, GstToc *toc)
{
GList *entries = gst_toc_get_entries (toc);
GList *ma, *markers = NULL;
GST_DEBUG_OBJECT (self, "Preparing markers from TOC: %" GST_PTR_FORMAT, toc);
_iterate_toc_entries (self, entries, &markers);
GST_OBJECT_LOCK (self);
g_ptr_array_remove_range (self->pending_markers, 0, self->pending_markers->len);
for (ma = markers; ma != NULL; ma = ma->next)
g_ptr_array_add (self->pending_markers, CLAPPER_MARKER_CAST (ma->data));
self->needs_refresh = TRUE;
GST_OBJECT_UNLOCK (self);
if (markers)
g_list_free (markers);
}
gboolean
clapper_timeline_set_toc (ClapperTimeline *self, GstToc *toc, gboolean updated)
{
gboolean changed;
if (gst_toc_get_scope (toc) != GST_TOC_SCOPE_GLOBAL)
return FALSE;
GST_OBJECT_LOCK (self);
if (self->toc == toc) {
changed = updated;
} else {
/* FIXME: Iterate and compare entries and their amount
* one by one, so we can avoid update between discovery and playback
* (and also when playing the same media item again) */
changed = TRUE;
}
if (changed) {
if (self->toc)
gst_toc_unref (self->toc);
self->toc = gst_toc_ref (toc);
}
GST_OBJECT_UNLOCK (self);
if (changed)
_prepare_markers (self, toc);
return changed;
}
/* Must be called from main thread */
void
clapper_timeline_refresh (ClapperTimeline *self)
{
GSequenceIter *iter;
GList *rec, *rec_markers = NULL;
gpointer *stolen_markers;
gsize n_pending = 0;
guint i, n_before, n_after;
GST_OBJECT_LOCK (self);
/* This prevents us from incorrect behaviour when there were multiple
* TOC objects set in a row before we reached main thread handling
* for them here and refresh will be now invoked in a row, possibly
* erasing markers on its second run */
if (!self->needs_refresh) {
GST_OBJECT_UNLOCK (self);
return;
}
GST_DEBUG_OBJECT (self, "Timeline refresh");
n_before = g_sequence_get_length (self->markers_seq);
/* Recover markers that should remain */
iter = g_sequence_get_begin_iter (self->markers_seq);
while (!g_sequence_iter_is_end (iter)) {
ClapperMarker *marker = CLAPPER_MARKER_CAST (g_sequence_get (iter));
if (!clapper_marker_is_internal (marker))
rec_markers = g_list_append (rec_markers, gst_object_ref (marker));
iter = g_sequence_iter_next (iter);
}
/* Clear sequence */
g_sequence_remove_range (
g_sequence_get_begin_iter (self->markers_seq),
g_sequence_get_end_iter (self->markers_seq));
/* Transfer pending markers into sequence */
stolen_markers = g_ptr_array_steal (self->pending_markers, &n_pending);
for (i = 0; i < n_pending; ++i) {
g_sequence_append (self->markers_seq, CLAPPER_MARKER_CAST (stolen_markers[i]));
gst_object_set_parent (GST_OBJECT_CAST (stolen_markers[i]), GST_OBJECT_CAST (self));
}
g_free (stolen_markers);
/* Transfer recovered markers back into sequence */
for (rec = rec_markers; rec != NULL; rec = rec->next) {
ClapperMarker *marker = CLAPPER_MARKER_CAST (rec->data);
g_sequence_append (self->markers_seq, marker);
gst_object_set_parent (GST_OBJECT_CAST (marker), GST_OBJECT_CAST (self));
}
if (rec_markers)
g_list_free (rec_markers);
/* Sort once after all appends (this way is faster according to documentation) */
g_sequence_sort (self->markers_seq, _markers_compare_func, NULL);
n_after = g_sequence_get_length (self->markers_seq);
self->needs_refresh = FALSE;
GST_OBJECT_UNLOCK (self);
GST_DEBUG_OBJECT (self, "Timeline refreshed, n_before: %u, n_after: %u",
n_before, n_after);
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_before, n_after);
if (n_before != n_after)
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_N_MARKERS]);
clapper_timeline_post_item_updated (self);
}
static void
_marker_remove_func (ClapperMarker *marker)
{
gst_object_unparent (GST_OBJECT_CAST (marker));
gst_object_unref (marker);
}
static void
clapper_timeline_init (ClapperTimeline *self)
{
self->markers_seq = g_sequence_new ((GDestroyNotify) _marker_remove_func);
self->pending_markers = g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref);
}
static void
clapper_timeline_finalize (GObject *object)
{
ClapperTimeline *self = CLAPPER_TIMELINE_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
g_sequence_free (self->markers_seq);
if (self->toc)
gst_toc_unref (self->toc);
g_ptr_array_unref (self->pending_markers);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_timeline_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperTimeline *self = CLAPPER_TIMELINE_CAST (object);
switch (prop_id) {
case PROP_N_MARKERS:
g_value_set_uint (value, clapper_timeline_get_n_markers (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_timeline_class_init (ClapperTimelineClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappertimeline", 0,
"Clapper Timeline");
gobject_class->get_property = clapper_timeline_get_property;
gobject_class->finalize = clapper_timeline_finalize;
/**
* ClapperTimeline:n-markers:
*
* Number of markers in the timeline.
*/
param_specs[PROP_N_MARKERS] = g_param_spec_uint ("n-markers",
NULL, NULL, 0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
}

View File

@@ -0,0 +1,47 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <clapper/clapper-marker.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_TIMELINE (clapper_timeline_get_type())
#define CLAPPER_TIMELINE_CAST(obj) ((ClapperTimeline *)(obj))
G_DECLARE_FINAL_TYPE (ClapperTimeline, clapper_timeline, CLAPPER, TIMELINE, GstObject)
gboolean clapper_timeline_insert_marker (ClapperTimeline *timeline, ClapperMarker *marker);
void clapper_timeline_remove_marker (ClapperTimeline *timeline, ClapperMarker *marker);
ClapperMarker * clapper_timeline_get_marker (ClapperTimeline *timeline, guint index);
guint clapper_timeline_get_n_markers (ClapperTimeline *timeline);
G_END_DECLS

View File

@@ -0,0 +1,54 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
#include <gst/gst.h>
#include "clapper-utils.h"
#include "clapper-queue.h"
#include "clapper-media-item.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
void clapper_utils_initialize (void);
G_GNUC_INTERNAL
void clapper_utils_queue_append_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item);
G_GNUC_INTERNAL
void clapper_utils_queue_insert_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item, ClapperMediaItem *after_item);
G_GNUC_INTERNAL
void clapper_utils_queue_remove_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item);
G_GNUC_INTERNAL
void clapper_utils_queue_clear_on_main_sync (ClapperQueue *queue);
G_GNUC_INTERNAL
gchar * clapper_utils_uri_from_file (GFile *file);
G_GNUC_INTERNAL
gchar * clapper_utils_title_from_uri (const gchar *uri);
G_END_DECLS

View File

@@ -0,0 +1,216 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "clapper-utils-private.h"
#include "../shared/clapper-shared-utils-private.h"
#define GST_CAT_DEFAULT clapper_utils_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
typedef enum
{
CLAPPER_UTILS_QUEUE_ALTER_APPEND = 1,
CLAPPER_UTILS_QUEUE_ALTER_INSERT,
CLAPPER_UTILS_QUEUE_ALTER_REMOVE,
CLAPPER_UTILS_QUEUE_ALTER_CLEAR
} ClapperUtilsQueueAlterMethod;
typedef struct
{
ClapperQueue *queue;
ClapperMediaItem *item;
ClapperMediaItem *after_item;
ClapperUtilsQueueAlterMethod method;
} ClapperUtilsQueueAlterData;
void
clapper_utils_initialize (void)
{
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperutils", 0,
"Clapper Utilities");
}
static ClapperUtilsQueueAlterData *
clapper_utils_queue_alter_data_new (ClapperQueue *queue,
ClapperMediaItem *item, ClapperMediaItem *after_item,
ClapperUtilsQueueAlterMethod method)
{
ClapperUtilsQueueAlterData *data = g_new (ClapperUtilsQueueAlterData, 1);
data->queue = queue;
data->item = item;
data->after_item = after_item;
data->method = method;
GST_TRACE ("Created queue alter data: %p", data);
return data;
}
static void
clapper_utils_queue_alter_data_free (ClapperUtilsQueueAlterData *data)
{
GST_TRACE ("Freeing queue alter data: %p", data);
g_free (data);
}
static gpointer
clapper_utils_queue_alter_on_main (ClapperUtilsQueueAlterData *data)
{
GST_DEBUG ("Queue alter invoked");
switch (data->method) {
case CLAPPER_UTILS_QUEUE_ALTER_APPEND:
clapper_queue_add_item (data->queue, data->item);
break;
case CLAPPER_UTILS_QUEUE_ALTER_INSERT:{
guint index;
/* If we have "after_item" then we need to insert after it, otherwise prepend */
if (data->after_item) {
if (clapper_queue_find_item (data->queue, data->after_item, &index))
index++;
else // If not found, just append at the end
index = -1;
} else {
index = 0;
}
clapper_queue_insert_item (data->queue, data->item, index);
break;
}
case CLAPPER_UTILS_QUEUE_ALTER_REMOVE:
clapper_queue_remove_item (data->queue, data->item);
break;
case CLAPPER_UTILS_QUEUE_ALTER_CLEAR:
clapper_queue_clear (data->queue);
break;
default:
g_assert_not_reached ();
break;
}
return NULL;
}
static inline void
clapper_utils_queue_alter_invoke_on_main_sync_take (ClapperUtilsQueueAlterData *data)
{
GST_DEBUG ("Invoking queue alter on main...");
clapper_shared_utils_context_invoke_sync_full (g_main_context_default (),
(GThreadFunc) clapper_utils_queue_alter_on_main, data,
(GDestroyNotify) clapper_utils_queue_alter_data_free);
GST_DEBUG ("Queue alter invoke finished");
}
void
clapper_utils_queue_append_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item)
{
ClapperUtilsQueueAlterData *data = clapper_utils_queue_alter_data_new (queue,
item, NULL, CLAPPER_UTILS_QUEUE_ALTER_APPEND);
clapper_utils_queue_alter_invoke_on_main_sync_take (data);
}
void
clapper_utils_queue_insert_on_main_sync (ClapperQueue *queue,
ClapperMediaItem *item, ClapperMediaItem *after_item)
{
ClapperUtilsQueueAlterData *data = clapper_utils_queue_alter_data_new (queue,
item, after_item, CLAPPER_UTILS_QUEUE_ALTER_INSERT);
clapper_utils_queue_alter_invoke_on_main_sync_take (data);
}
void
clapper_utils_queue_remove_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item)
{
ClapperUtilsQueueAlterData *data = clapper_utils_queue_alter_data_new (queue,
item, NULL, CLAPPER_UTILS_QUEUE_ALTER_REMOVE);
clapper_utils_queue_alter_invoke_on_main_sync_take (data);
}
void
clapper_utils_queue_clear_on_main_sync (ClapperQueue *queue)
{
ClapperUtilsQueueAlterData *data = clapper_utils_queue_alter_data_new (queue,
NULL, NULL, CLAPPER_UTILS_QUEUE_ALTER_CLEAR);
clapper_utils_queue_alter_invoke_on_main_sync_take (data);
}
gchar *
clapper_utils_uri_from_file (GFile *file)
{
gchar *uri = g_file_get_uri (file);
gsize length = strlen (uri);
/* GFile might incorrectly append "/" at the end of an URI,
* remove it to make it work with GStreamer URI handling */
if (uri[length - 1] == '/') {
gchar *fixed_uri;
/* NULL terminated copy without last character */
fixed_uri = g_new0 (gchar, length);
memcpy (fixed_uri, uri, length - 1);
g_free (uri);
uri = fixed_uri;
}
return uri;
}
gchar *
clapper_utils_title_from_uri (const gchar *uri)
{
gchar *proto, *title = NULL;
proto = gst_uri_get_protocol (uri);
if (G_UNLIKELY (proto == NULL))
return NULL;
if (strcmp (proto, "file") == 0) {
gchar *filename = g_filename_from_uri (uri, NULL, NULL);
if (filename) {
const gchar *ext;
title = g_path_get_basename (filename);
ext = strrchr (title, '.');
g_free (filename);
if (ext && strlen (ext) <= 4) {
gchar *tmp = g_strndup (title, strlen (title) - strlen (ext));
g_free (title);
title = tmp;
}
}
} else if (strcmp (proto, "dvb") == 0) {
const gchar *channel = strrchr (uri, '/') + 1;
title = g_strdup (channel);
}
g_free (proto);
return title;
}

View File

@@ -0,0 +1,81 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <gst/gst.h>
G_BEGIN_DECLS
/**
* CLAPPER_TIME_FORMAT: (skip):
*
* A string that can be used in printf-like format to display
* e.g. position or duration in `hh:mm:ss` format. Meant to be
* used together with [func@Clapper.TIME_ARGS].
*
* Example:
*
* ```c
* gchar *str = g_strdup_printf ("%" CLAPPER_TIME_FORMAT, CLAPPER_TIME_ARGS (time));
* ```
*/
#define CLAPPER_TIME_FORMAT "02u:%02u:%02u"
/**
* CLAPPER_TIME_ARGS: (skip):
* @t: time value in seconds
*
* Formats @t for the [const@Clapper.TIME_FORMAT] format string.
*/
#define CLAPPER_TIME_ARGS(t) \
(guint) (((GstClockTime)(t)) / 3600), \
(guint) ((((GstClockTime)(t)) / 60) % 60), \
(guint) (((GstClockTime)(t)) % 60)
/**
* CLAPPER_TIME_MS_FORMAT: (skip):
*
* Same as [const@Clapper.TIME_FORMAT], but also displays milliseconds.
* Meant to be used together with [func@Clapper.TIME_MS_ARGS].
*
* Example:
*
* ```c
* gchar *str = g_strdup_printf ("%" CLAPPER_TIME_MS_FORMAT, CLAPPER_TIME_MS_ARGS (time));
* ```
*/
#define CLAPPER_TIME_MS_FORMAT "02u:%02u:%02u.%03u"
/**
* CLAPPER_TIME_MS_ARGS: (skip):
* @t: time value in seconds
*
* Formats @t for the [const@Clapper.TIME_MS_FORMAT] format string.
*/
#define CLAPPER_TIME_MS_ARGS(t) \
CLAPPER_TIME_ARGS(t), \
(guint) (((GstClockTime)(t * 1000)) % 1000)
G_END_DECLS

View File

@@ -0,0 +1,76 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
/**
* CLAPPER_MAJOR_VERSION:
*
* Clapper major version component
*/
#define CLAPPER_MAJOR_VERSION (@CLAPPER_MAJOR_VERSION@)
/**
* CLAPPER_MINOR_VERSION:
*
* Clapper minor version component
*/
#define CLAPPER_MINOR_VERSION (@CLAPPER_MINOR_VERSION@)
/**
* CLAPPER_MICRO_VERSION:
*
* Clapper micro version component
*/
#define CLAPPER_MICRO_VERSION (@CLAPPER_MICRO_VERSION@)
/**
* CLAPPER_VERSION:
*
* Clapper version
*/
#define CLAPPER_VERSION (@CLAPPER_VERSION@)
/**
* CLAPPER_VERSION_S:
*
* Clapper version, encoded as a string
*/
#define CLAPPER_VERSION_S "@CLAPPER_VERSION@"
#define CLAPPER_ENCODE_VERSION(major,minor,micro) \
((major) << 24 | (minor) << 16 | (micro) << 8)
/**
* CLAPPER_VERSION_HEX:
*
* Clapper version, encoded as an hexadecimal number, useful for integer comparisons.
*/
#define CLAPPER_VERSION_HEX \
(CLAPPER_ENCODE_VERSION (CLAPPER_MAJOR_VERSION, CLAPPER_MINOR_VERSION, CLAPPER_MICRO_VERSION))
#define CLAPPER_CHECK_VERSION(major, minor, micro) \
(CLAPPER_MAJOR_VERSION > (major) || \
(CLAPPER_MAJOR_VERSION == (major) && CLAPPER_MINOR_VERSION > (minor)) || \
(CLAPPER_MAJOR_VERSION == (major) && CLAPPER_MINOR_VERSION == (minor) && \
CLAPPER_MICRO_VERSION >= (micro)))

View File

@@ -0,0 +1,32 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gst/gst.h>
#include "clapper-video-stream.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
ClapperStream * clapper_video_stream_new (GstStream *gst_stream);
G_END_DECLS

View File

@@ -0,0 +1,385 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperVideoStream:
*
* Represents a video stream within media.
*/
#include "clapper-video-stream-private.h"
#include "clapper-stream-private.h"
#define GST_CAT_DEFAULT clapper_video_stream_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperVideoStream
{
ClapperStream parent;
gchar *codec;
gint width;
gint height;
gdouble fps;
guint bitrate;
gchar *pixel_format;
};
#define parent_class clapper_video_stream_parent_class
G_DEFINE_TYPE (ClapperVideoStream, clapper_video_stream, CLAPPER_TYPE_STREAM);
enum
{
PROP_0,
PROP_CODEC,
PROP_WIDTH,
PROP_HEIGHT,
PROP_FPS,
PROP_BITRATE,
PROP_PIXEL_FORMAT,
PROP_LAST
};
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static void
_update_using_caps (ClapperVideoStream *self, GstCaps *caps)
{
ClapperStream *stream = CLAPPER_STREAM_CAST (self);
GstStructure *structure;
gint width = 0, height = 0, fps_n = 0, fps_d = 0;
if (gst_caps_get_size (caps) == 0)
return;
structure = gst_caps_get_structure (caps, 0);
/* NOTE: We cannot use gst_structure_get() here,
* as it stops iterating on first not found key */
gst_structure_get_int (structure, "width", &width);
clapper_stream_set_int_prop (stream, param_specs[PROP_WIDTH], &self->width, width);
gst_structure_get_int (structure, "height", &height);
clapper_stream_set_int_prop (stream, param_specs[PROP_HEIGHT], &self->height, height);
gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d);
if (G_UNLIKELY (fps_d == 0))
fps_d = 1;
clapper_stream_set_double_prop (stream, param_specs[PROP_FPS], &self->fps, (gdouble) fps_n / fps_d);
clapper_stream_set_string_prop (stream, param_specs[PROP_PIXEL_FORMAT], &self->pixel_format,
gst_structure_get_string (structure, "format"));
}
static void
_update_using_tags (ClapperVideoStream *self, GstTagList *tags)
{
ClapperStream *stream = CLAPPER_STREAM_CAST (self);
gchar *codec = NULL;
guint bitrate = 0;
gst_tag_list_get_string_index (tags, GST_TAG_VIDEO_CODEC, 0, &codec);
clapper_stream_take_string_prop (stream, param_specs[PROP_CODEC], &self->codec, codec);
gst_tag_list_get_uint_index (tags, GST_TAG_BITRATE, 0, &bitrate);
clapper_stream_set_uint_prop (stream, param_specs[PROP_BITRATE], &self->bitrate, bitrate);
}
ClapperStream *
clapper_video_stream_new (GstStream *gst_stream)
{
ClapperVideoStream *video_stream;
video_stream = g_object_new (CLAPPER_TYPE_VIDEO_STREAM,
"stream-type", CLAPPER_STREAM_TYPE_VIDEO, NULL);
gst_object_ref_sink (video_stream);
clapper_stream_set_gst_stream (CLAPPER_STREAM_CAST (video_stream), gst_stream);
return CLAPPER_STREAM_CAST (video_stream);
}
/**
* clapper_video_stream_get_codec:
* @stream: a #ClapperVideoStream
*
* Get codec used to encode @stream.
*
* Returns: (transfer full) (nullable): the video codec of stream
* or %NULL if undetermined.
*/
gchar *
clapper_video_stream_get_codec (ClapperVideoStream *self)
{
gchar *codec;
g_return_val_if_fail (CLAPPER_IS_VIDEO_STREAM (self), NULL);
GST_OBJECT_LOCK (self);
codec = g_strdup (self->codec);
GST_OBJECT_UNLOCK (self);
return codec;
}
/**
* clapper_video_stream_get_width:
* @stream: a #ClapperVideoStream
*
* Get width of video @stream.
*
* Returns: the width of video stream.
*/
gint
clapper_video_stream_get_width (ClapperVideoStream *self)
{
gint width;
g_return_val_if_fail (CLAPPER_IS_VIDEO_STREAM (self), 0);
GST_OBJECT_LOCK (self);
width = self->width;
GST_OBJECT_UNLOCK (self);
return width;
}
/**
* clapper_video_stream_get_height:
* @stream: a #ClapperVideoStream
*
* Get height of video @stream.
*
* Returns: the height of video stream.
*/
gint
clapper_video_stream_get_height (ClapperVideoStream *self)
{
gint height;
g_return_val_if_fail (CLAPPER_IS_VIDEO_STREAM (self), 0);
GST_OBJECT_LOCK (self);
height = self->height;
GST_OBJECT_UNLOCK (self);
return height;
}
/**
* clapper_video_stream_get_fps:
* @stream: a #ClapperVideoStream
*
* Get number of frames per second in video @stream.
*
* Returns: the FPS of video stream.
*/
gdouble
clapper_video_stream_get_fps (ClapperVideoStream *self)
{
gdouble fps;
g_return_val_if_fail (CLAPPER_IS_VIDEO_STREAM (self), 0);
GST_OBJECT_LOCK (self);
fps = self->fps;
GST_OBJECT_UNLOCK (self);
return fps;
}
/**
* clapper_video_stream_get_bitrate:
* @stream: a #ClapperVideoStream
*
* Get bitrate of video @stream.
*
* Returns: the bitrate of video stream.
*/
guint
clapper_video_stream_get_bitrate (ClapperVideoStream *self)
{
guint bitrate;
g_return_val_if_fail (CLAPPER_IS_VIDEO_STREAM (self), 0);
GST_OBJECT_LOCK (self);
bitrate = self->bitrate;
GST_OBJECT_UNLOCK (self);
return bitrate;
}
/**
* clapper_video_stream_get_pixel_format:
* @stream: a #ClapperVideoStream
*
* Get pixel format of video @stream.
*
* Returns: (transfer full) (nullable): the pixel format of stream
* or %NULL if undetermined.
*/
gchar *
clapper_video_stream_get_pixel_format (ClapperVideoStream *self)
{
gchar *pixel_format;
g_return_val_if_fail (CLAPPER_IS_VIDEO_STREAM (self), NULL);
GST_OBJECT_LOCK (self);
pixel_format = g_strdup (self->pixel_format);
GST_OBJECT_UNLOCK (self);
return pixel_format;
}
static void
clapper_video_stream_init (ClapperVideoStream *self)
{
}
static void
clapper_video_stream_finalize (GObject *object)
{
ClapperVideoStream *self = CLAPPER_VIDEO_STREAM_CAST (object);
g_free (self->codec);
g_free (self->pixel_format);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_video_stream_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperVideoStream *self = CLAPPER_VIDEO_STREAM_CAST (object);
switch (prop_id) {
case PROP_CODEC:
g_value_take_string (value, clapper_video_stream_get_codec (self));
break;
case PROP_WIDTH:
g_value_set_int (value, clapper_video_stream_get_width (self));
break;
case PROP_HEIGHT:
g_value_set_int (value, clapper_video_stream_get_height (self));
break;
case PROP_FPS:
g_value_set_double (value, clapper_video_stream_get_fps (self));
break;
case PROP_BITRATE:
g_value_set_uint (value, clapper_video_stream_get_bitrate (self));
break;
case PROP_PIXEL_FORMAT:
g_value_take_string (value, clapper_video_stream_get_pixel_format (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_video_stream_internal_stream_updated (ClapperStream *stream,
GstCaps *caps, GstTagList *tags)
{
ClapperVideoStream *self = CLAPPER_VIDEO_STREAM_CAST (stream);
CLAPPER_STREAM_CLASS (parent_class)->internal_stream_updated (stream, caps, tags);
if (caps)
_update_using_caps (self, caps);
if (tags)
_update_using_tags (self, tags);
}
static void
clapper_video_stream_class_init (ClapperVideoStreamClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
ClapperStreamClass *stream_class = (ClapperStreamClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappervideostream", 0,
"Clapper Video Stream");
gobject_class->get_property = clapper_video_stream_get_property;
gobject_class->finalize = clapper_video_stream_finalize;
stream_class->internal_stream_updated = clapper_video_stream_internal_stream_updated;
/**
* ClapperVideoStream:codec:
*
* Stream codec.
*/
param_specs[PROP_CODEC] = g_param_spec_string ("codec",
NULL, NULL, NULL,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperVideoStream:width:
*
* Stream width.
*/
param_specs[PROP_WIDTH] = g_param_spec_int ("width",
NULL, NULL, 0, G_MAXINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperVideoStream:height:
*
* Stream height.
*/
param_specs[PROP_HEIGHT] = g_param_spec_int ("height",
NULL, NULL, 0, G_MAXINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperVideoStream:fps:
*
* Stream FPS.
*/
param_specs[PROP_FPS] = g_param_spec_double ("fps",
NULL, NULL, 0, G_MAXDOUBLE, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperVideoStream:bitrate:
*
* Stream bitrate.
*/
param_specs[PROP_BITRATE] = g_param_spec_uint ("bitrate",
NULL, NULL, 0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperVideoStream:pixel-format:
*
* Stream pixel format.
*/
param_specs[PROP_PIXEL_FORMAT] = g_param_spec_string ("pixel-format",
NULL, NULL, NULL,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
}

View File

@@ -0,0 +1,49 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <clapper/clapper-stream.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_VIDEO_STREAM (clapper_video_stream_get_type())
#define CLAPPER_VIDEO_STREAM_CAST(obj) ((ClapperVideoStream *)(obj))
G_DECLARE_FINAL_TYPE (ClapperVideoStream, clapper_video_stream, CLAPPER, VIDEO_STREAM, ClapperStream)
gchar * clapper_video_stream_get_codec (ClapperVideoStream *stream);
gint clapper_video_stream_get_width (ClapperVideoStream *stream);
gint clapper_video_stream_get_height (ClapperVideoStream *stream);
gdouble clapper_video_stream_get_fps (ClapperVideoStream *stream);
guint clapper_video_stream_get_bitrate (ClapperVideoStream *stream);
gchar * clapper_video_stream_get_pixel_format (ClapperVideoStream *stream);
G_END_DECLS

94
src/lib/clapper/clapper.c Normal file
View File

@@ -0,0 +1,94 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include "clapper.h"
#include "clapper-utils-private.h"
#include "clapper-playbin-bus-private.h"
#include "clapper-app-bus-private.h"
#include "clapper-features-bus-private.h"
static gboolean is_initialized = FALSE;
static GMutex init_lock;
static gboolean
clapper_init_check_internal (int *argc, char **argv[])
{
g_mutex_lock (&init_lock);
if (is_initialized || !gst_init_check (argc, argv, NULL))
goto finish;
gst_pb_utils_init ();
clapper_utils_initialize ();
clapper_playbin_bus_initialize ();
clapper_app_bus_initialize ();
clapper_features_bus_initialize ();
is_initialized = TRUE;
finish:
g_mutex_unlock (&init_lock);
return is_initialized;
}
/**
* clapper_init:
* @argc: (inout) (nullable) (optional): pointer to application's argc
* @argv: (inout) (array length=argc) (nullable) (optional): pointer to application's argv
*
* Initializes the Clapper library. Implementations must always call this
* before using Clapper API.
*
* Because Clapper uses GStreamer internally, this function will also initialize
* GStreamer before initializing Clapper itself for user convienience, so
* application does not have to do so anymore.
*
* WARNING: This function will terminate your program if it was unable to
* initialize for some reason. If you want to do some fallback logic,
* use [func@Clapper.init_check] instead.
*/
void
clapper_init (int *argc, char **argv[])
{
if (!clapper_init_check_internal (argc, argv)) {
g_printerr ("Could not initialize Clapper library\n");
exit (1);
}
}
/**
* clapper_init_check:
* @argc: (inout) (nullable) (optional): pointer to application's argc
* @argv: (inout) (array length=argc) (nullable) (optional): pointer to application's argv
*
* This function does the same thing as [func@Clapper.init], but instead of
* terminating on failure it returns %FALSE with @error set.
*
* Returns: %TRUE if Clapper could be initialized, %FALSE otherwise.
*/
gboolean
clapper_init_check (int *argc, char **argv[])
{
return clapper_init_check_internal (argc, argv);
}

61
src/lib/clapper/clapper.h Normal file
View File

@@ -0,0 +1,61 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#define __CLAPPER_INSIDE__
#include <clapper/clapper-enums.h>
#include <clapper/clapper-version.h>
#include <clapper/clapper-audio-stream.h>
#include <clapper/clapper-feature.h>
#include <clapper/clapper-marker.h>
#include <clapper/clapper-media-item.h>
#include <clapper/clapper-player.h>
#include <clapper/clapper-queue.h>
#include <clapper/clapper-stream.h>
#include <clapper/clapper-stream-list.h>
#include <clapper/clapper-subtitle-stream.h>
#include <clapper/clapper-threaded-object.h>
#include <clapper/clapper-timeline.h>
#include <clapper/clapper-utils.h>
#include <clapper/clapper-video-stream.h>
#include <clapper/features/clapper-features-availability.h>
#if CLAPPER_HAVE_DISCOVERER
#include <clapper/features/discoverer/clapper-discoverer.h>
#endif
#if CLAPPER_HAVE_MPRIS
#include <clapper/features/mpris/clapper-mpris.h>
#endif
#if CLAPPER_HAVE_SERVER
#include <clapper/features/server/clapper-server.h>
#endif
G_BEGIN_DECLS
void clapper_init (int *argc, char **argv[]);
gboolean clapper_init_check (int *argc, char **argv[]);
G_END_DECLS
#undef __CLAPPER_INSIDE__

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
/**
* CLAPPER_HAVE_DISCOVERER:
*
* Check if Clapper was compiled with Discoverer feature.
*/
#define CLAPPER_HAVE_DISCOVERER (@CLAPPER_HAVE_DISCOVERER@)
/**
* CLAPPER_HAVE_MPRIS:
*
* Check if Clapper was compiled with MPRIS feature.
*/
#define CLAPPER_HAVE_MPRIS (@CLAPPER_HAVE_MPRIS@)
/**
* CLAPPER_HAVE_SERVER:
*
* Check if Clapper was compiled with Server feature.
*/
#define CLAPPER_HAVE_SERVER (@CLAPPER_HAVE_SERVER@)

View File

@@ -0,0 +1,496 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* ClapperDiscoverer:
*
* An optional Discoverer feature to be added to the player.
*
* #ClapperDiscoverer is a feature that wraps around #GstDiscoverer API
* to automatically discover items within [class@Clapper.Queue]. Once media
* is scanned, all extra information of it will be filled within media item,
* this includes title, duration, chapters, etc.
*
* Please note that media items are also discovered during their playback
* by the player itself. #ClapperDiscoverer is useful in situations where
* one wants to present to the user an updated media item before its
* playback, such as an UI that displays playback queue.
*
* Depending on your application, you can select an optimal
* [enum@Clapper.DiscovererDiscoveryMode] that best suits your needs.
*
* Use [const@Clapper.HAVE_DISCOVERER] macro to check if Clapper API
* was compiled with this feature.
*/
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include "clapper-discoverer.h"
#include "clapper-queue.h"
#include "clapper-media-item-private.h"
#include "../shared/clapper-shared-utils-private.h"
#define DEFAULT_DISCOVERY_MODE CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT
#define GST_CAT_DEFAULT clapper_discoverer_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperDiscoverer
{
ClapperFeature parent;
GstDiscoverer *discoverer;
GPtrArray *pending_items;
ClapperMediaItem *discovered_item;
gboolean running;
GSource *timeout_source;
ClapperDiscovererDiscoveryMode discovery_mode;
};
enum
{
PROP_0,
PROP_DISCOVERY_MODE,
PROP_LAST
};
#define parent_class clapper_discoverer_parent_class
G_DEFINE_TYPE (ClapperDiscoverer, clapper_discoverer, CLAPPER_TYPE_FEATURE);
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static inline void
_clear_timeout_source (ClapperDiscoverer *self)
{
if (self->timeout_source) {
g_source_destroy (self->timeout_source);
g_clear_pointer (&self->timeout_source, g_source_unref);
}
}
static inline void
_unqueue_discovery (ClapperDiscoverer *self, ClapperMediaItem *item)
{
guint index = 0;
/* Removing item that is being discovered */
if (item == self->discovered_item) {
GST_DEBUG_OBJECT (self, "Ignoring discovery of current item %" GST_PTR_FORMAT, item);
gst_clear_object (&self->discovered_item);
} else if (g_ptr_array_find (self->pending_items, item, &index)) {
GST_DEBUG_OBJECT (self, "Removing discovery of pending item %" GST_PTR_FORMAT, item);
g_ptr_array_remove_index (self->pending_items, index);
}
}
static inline void
_start_discovery (ClapperDiscoverer *self)
{
if (!self->running) {
gst_discoverer_start (self->discoverer);
self->running = TRUE;
GST_INFO_OBJECT (self, "Discoverer started");
}
}
static inline void
_stop_discovery (ClapperDiscoverer *self)
{
if (self->running) {
gst_discoverer_stop (self->discoverer);
self->running = FALSE;
GST_INFO_OBJECT (self, "Discoverer stopped");
}
}
static void
_run_discovery (ClapperDiscoverer *self)
{
ClapperMediaItem *item;
ClapperQueue *queue;
ClapperDiscovererDiscoveryMode discovery_mode;
const gchar *uri;
gboolean success = FALSE;
if (self->pending_items->len == 0) {
GST_DEBUG_OBJECT (self, "No more pending items");
return;
}
item = g_ptr_array_steal_index (self->pending_items, 0);
GST_DEBUG_OBJECT (self, "Investigating discovery of %" GST_PTR_FORMAT, item);
queue = CLAPPER_QUEUE_CAST (gst_object_get_parent (GST_OBJECT_CAST (item)));
if (G_UNLIKELY (queue == NULL)) {
GST_DEBUG_OBJECT (self, "Queued item %" GST_PTR_FORMAT
" does not appear to be in queue anymore", item);
goto finish;
}
discovery_mode = clapper_discoverer_get_discovery_mode (self);
if (discovery_mode == CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT
&& clapper_queue_item_is_current (queue, item)) {
GST_DEBUG_OBJECT (self, "Queued %" GST_PTR_FORMAT
" is current item, ignoring discovery", item);
goto finish;
}
uri = clapper_media_item_get_uri (item);
GST_DEBUG_OBJECT (self, "Starting discovery of %"
GST_PTR_FORMAT "(%s)", item, uri);
/* Need to start first, then append URI */
_start_discovery (self);
if ((success = gst_discoverer_discover_uri_async (self->discoverer, uri))) {
gst_object_replace ((GstObject **) &self->discovered_item, GST_OBJECT_CAST (item));
GST_DEBUG_OBJECT (self, "Running discovery of %"
GST_PTR_FORMAT "(%s)", self->discovered_item, uri);
} else {
GST_ERROR_OBJECT (self, "Could not run discovery of %"
GST_PTR_FORMAT "(%s)", item, uri);
}
finish:
gst_clear_object (&item);
gst_clear_object (&queue);
/* Continue until we run out of pending items */
if (!success)
_run_discovery (self);
}
static gboolean
_run_discovery_delayed_cb (ClapperDiscoverer *self)
{
GST_DEBUG_OBJECT (self, "Delayed discovery handler reached");
_clear_timeout_source (self);
_run_discovery (self);
return G_SOURCE_REMOVE;
}
static void
_discovered_cb (GstDiscoverer *discoverer G_GNUC_UNUSED,
GstDiscovererInfo *info, GError *error, ClapperDiscoverer *self)
{
/* Can be NULL if removed while discovery of it was running */
if (self->discovered_item) {
const gchar *uri = clapper_media_item_get_uri (self->discovered_item);
if (G_LIKELY (error == NULL)) {
GST_DEBUG_OBJECT (self, "Finished discovery of %"
GST_PTR_FORMAT "(%s)", self->discovered_item, uri);
clapper_media_item_update_from_discoverer_info (self->discovered_item, info);
} else {
GST_ERROR_OBJECT (self, "Discovery of %" GST_PTR_FORMAT
"(%s) failed, reason: %s", self->discovered_item, uri, error->message);
}
/* Clear so its NULL when replaced later */
gst_clear_object (&self->discovered_item);
}
/* Try to discover next item */
_run_discovery (self);
}
static void
_finished_cb (GstDiscoverer *discoverer G_GNUC_UNUSED, ClapperDiscoverer *self)
{
if (G_LIKELY (self->pending_items->len == 0)) {
GST_DEBUG_OBJECT (self, "Finished discovery of all items");
} else {
/* This should never happen, but if it does, then clear
* pending items array so we can somewhat recover */
GST_ERROR_OBJECT (self, "Discovery stopped, but still had %u pending items!",
self->pending_items->len);
g_ptr_array_remove_range (self->pending_items, 0, self->pending_items->len);
}
_stop_discovery (self);
}
static void
clapper_discoverer_played_item_changed (ClapperFeature *feature, ClapperMediaItem *item)
{
ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (feature);
GST_DEBUG_OBJECT (self, "Played item changed to: %" GST_PTR_FORMAT, item);
_unqueue_discovery (self, item);
}
static void
clapper_discoverer_queue_item_added (ClapperFeature *feature, ClapperMediaItem *item, guint index)
{
ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (feature);
GST_DEBUG_OBJECT (self, "Queue item added %" GST_PTR_FORMAT, item);
g_ptr_array_add (self->pending_items, gst_object_ref (item));
/* Already running, nothing more to do */
if (self->running)
return;
/* Need to always clear timeout here, as mode may
* have changed between adding multiple items */
_clear_timeout_source (self);
switch (clapper_discoverer_get_discovery_mode (self)) {
case CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT:
/* We start running after small delay in this mode, so
* application can select item after adding it to queue first */
self->timeout_source = clapper_shared_utils_context_timeout_add_full (
g_main_context_get_thread_default (),
G_PRIORITY_DEFAULT_IDLE, 50,
(GSourceFunc) _run_discovery_delayed_cb,
self, NULL);
break;
case CLAPPER_DISCOVERER_DISCOVERY_ALWAYS:
_run_discovery (self);
break;
default:
g_assert_not_reached ();
break;
}
}
static void
clapper_discoverer_queue_item_removed (ClapperFeature *feature, ClapperMediaItem *item, guint index)
{
ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (feature);
GST_DEBUG_OBJECT (self, "Queue item removed %" GST_PTR_FORMAT, item);
_unqueue_discovery (self, item);
}
static void
clapper_discoverer_queue_cleared (ClapperFeature *feature)
{
ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (feature);
GST_DEBUG_OBJECT (self, "Discarding discovery of all pending items");
if (self->pending_items->len > 0)
g_ptr_array_remove_range (self->pending_items, 0, self->pending_items->len);
gst_clear_object (&self->discovered_item);
_stop_discovery (self);
}
static gboolean
clapper_discoverer_prepare (ClapperFeature *feature)
{
ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (feature);
GError *error = NULL;
GST_DEBUG_OBJECT (self, "Prepare");
self->discoverer = gst_discoverer_new (15 * GST_SECOND, &error);
if (G_UNLIKELY (error != NULL)) {
GST_ERROR_OBJECT (self, "Could not prepare, reason: %s", error->message);
g_error_free (error);
return FALSE;
}
GST_TRACE_OBJECT (self, "Created new GstDiscoverer: %" GST_PTR_FORMAT, self->discoverer);
/* FIXME: Caching in GStreamer is broken. Does not save container tags, such as media title.
* Disable it until completely fixed upsteam. Once fixed change to %TRUE. */
g_object_set (self->discoverer, "use-cache", FALSE, NULL);
g_signal_connect (self->discoverer, "discovered",
G_CALLBACK (_discovered_cb), self);
g_signal_connect (self->discoverer, "finished",
G_CALLBACK (_finished_cb), self);
return TRUE;
}
static gboolean
clapper_discoverer_unprepare (ClapperFeature *feature)
{
ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (feature);
GST_DEBUG_OBJECT (self, "Unprepare");
_clear_timeout_source (self);
/* Do what we also do when queue is cleared */
clapper_discoverer_queue_cleared (feature);
gst_clear_object (&self->discoverer);
return TRUE;
}
/**
* clapper_discoverer_new:
*
* Creates a new #ClapperDiscoverer instance.
*
* Returns: (transfer full): a new #ClapperDiscoverer instance.
*/
ClapperDiscoverer *
clapper_discoverer_new (void)
{
ClapperDiscoverer *discoverer = g_object_new (CLAPPER_TYPE_DISCOVERER, NULL);
gst_object_ref_sink (discoverer);
return discoverer;
}
/**
* clapper_discoverer_set_discovery_mode:
* @discoverer: a #ClapperDiscoverer
* @mode: a #ClapperDiscovererDiscoveryMode
*
* Set the [enum@Clapper.DiscovererDiscoveryMode] of @discoverer.
*/
void
clapper_discoverer_set_discovery_mode (ClapperDiscoverer *self, ClapperDiscovererDiscoveryMode mode)
{
gboolean changed;
g_return_if_fail (CLAPPER_IS_DISCOVERER (self));
GST_OBJECT_LOCK (self);
if ((changed = self->discovery_mode != mode))
self->discovery_mode = mode;
GST_OBJECT_UNLOCK (self);
if (changed)
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_DISCOVERY_MODE]);
}
/**
* clapper_discoverer_get_discovery_mode:
* @discoverer: a #ClapperDiscoverer
*
* Get the [enum@Clapper.DiscovererDiscoveryMode] of @discoverer.
*
* Returns: a currently set #ClapperDiscovererDiscoveryMode.
*/
ClapperDiscovererDiscoveryMode
clapper_discoverer_get_discovery_mode (ClapperDiscoverer *self)
{
ClapperDiscovererDiscoveryMode mode;
g_return_val_if_fail (CLAPPER_IS_DISCOVERER (self), DEFAULT_DISCOVERY_MODE);
GST_OBJECT_LOCK (self);
mode = self->discovery_mode;
GST_OBJECT_UNLOCK (self);
return mode;
}
static void
clapper_discoverer_init (ClapperDiscoverer *self)
{
self->pending_items = g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref);
self->discovery_mode = DEFAULT_DISCOVERY_MODE;
}
static void
clapper_discoverer_finalize (GObject *object)
{
ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (object);
g_ptr_array_unref (self->pending_items);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_discoverer_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (object);
switch (prop_id) {
case PROP_DISCOVERY_MODE:
clapper_discoverer_set_discovery_mode (self, g_value_get_enum (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_discoverer_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (object);
switch (prop_id) {
case PROP_DISCOVERY_MODE:
g_value_set_enum (value, clapper_discoverer_get_discovery_mode (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_discoverer_class_init (ClapperDiscovererClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
ClapperFeatureClass *feature_class = (ClapperFeatureClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperdiscoverer", 0,
"Clapper Discoverer");
gobject_class->get_property = clapper_discoverer_get_property;
gobject_class->set_property = clapper_discoverer_set_property;
gobject_class->finalize = clapper_discoverer_finalize;
feature_class->prepare = clapper_discoverer_prepare;
feature_class->unprepare = clapper_discoverer_unprepare;
feature_class->played_item_changed = clapper_discoverer_played_item_changed;
feature_class->queue_item_added = clapper_discoverer_queue_item_added;
feature_class->queue_item_removed = clapper_discoverer_queue_item_removed;
feature_class->queue_cleared = clapper_discoverer_queue_cleared;
/**
* ClapperDiscoverer:discovery-mode:
*
* Discoverer discovery mode.
*/
param_specs[PROP_DISCOVERY_MODE] = g_param_spec_enum ("discovery-mode",
NULL, NULL, CLAPPER_TYPE_DISCOVERER_DISCOVERY_MODE, DEFAULT_DISCOVERY_MODE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <clapper/clapper-feature.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_DISCOVERER (clapper_discoverer_get_type())
#define CLAPPER_DISCOVERER_CAST(obj) ((ClapperDiscoverer *)(obj))
G_DECLARE_FINAL_TYPE (ClapperDiscoverer, clapper_discoverer, CLAPPER, DISCOVERER, ClapperFeature)
ClapperDiscoverer * clapper_discoverer_new (void);
void clapper_discoverer_set_discovery_mode (ClapperDiscoverer *discoverer, ClapperDiscovererDiscoveryMode mode);
ClapperDiscovererDiscoveryMode clapper_discoverer_get_discovery_mode (ClapperDiscoverer *discoverer);
G_END_DECLS

View File

@@ -0,0 +1,17 @@
feature_option = get_option(feature_name)
if feature_option.disabled()
subdir_done()
endif
clapper_features_headers += [
'features/discoverer/clapper-discoverer.h',
]
clapper_features_sources += [
'features/discoverer/clapper-discoverer.c',
]
install_headers('clapper-discoverer.h',
install_dir: join_paths(clapper_headers_dir, 'features', 'discoverer'),
)
clapper_available_features += feature_name

View File

@@ -0,0 +1,32 @@
clapper_features_headers = []
clapper_features_sources = []
clapper_features_sources_internal = []
clapper_features_deps = []
clapper_available_features = []
features_availability_conf = configuration_data()
clapper_possible_features = [
'discoverer',
'mpris',
'server',
]
foreach feature_name : clapper_possible_features
subdir(feature_name)
features_availability_conf.set(
'CLAPPER_HAVE_@0@'.format(feature_name.to_upper()),
clapper_available_features.contains(feature_name) ? 'TRUE' : 'FALSE'
)
endforeach
clapper_features_availability_header = configure_file(
input: 'clapper-features-availability.h.in',
output: 'clapper-features-availability.h',
configuration: features_availability_conf,
)
install_headers(clapper_features_availability_header,
install_dir: join_paths(clapper_headers_dir, 'features'),
)
clapper_features_headers += [
clapper_features_availability_header
]

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<node>
<interface name="org.mpris.MediaPlayer2">
<method name="Raise"/>
<method name="Quit"/>
<property name="CanQuit" type="b" access="read"/>
<property name="Fullscreen" type="b" access="readwrite"/>
<property name="CanSetFullscreen" type="b" access="read"/>
<property name="CanRaise" type="b" access="read"/>
<property name="HasTrackList" type="b" access="read"/>
<property name="Identity" type="s" access="read"/>
<property name="DesktopEntry" type="s" access="read"/>
<property name="SupportedUriSchemes" type="as" access="read"/>
<property name="SupportedMimeTypes" type="as" access="read"/>
</interface>
<interface name="org.mpris.MediaPlayer2.Player">
<method name="Next"/>
<method name="Previous"/>
<method name="Pause"/>
<method name="PlayPause"/>
<method name="Stop"/>
<method name="Play"/>
<method name="Seek">
<arg name="Offset" type="x" direction="in"/>
</method>
<method name="SetPosition">
<arg name="TrackId" type="o" direction="in"/>
<arg name="Position" type="x" direction="in"/>
</method>
<method name="OpenUri">
<arg name="Uri" type="s" direction="in"/>
</method>
<signal name="Seeked">
<arg name="Position" type="x"/>
</signal>
<property name="PlaybackStatus" type="s" access="read"/>
<property name="LoopStatus" type="s" access="readwrite"/>
<property name="Rate" type="d" access="readwrite"/>
<property name="Shuffle" type="b" access="readwrite"/>
<property name="Metadata" type="a{sv}" access="read"/>
<property name="Volume" type="d" access="readwrite"/>
<property name="Position" type="x" access="read"/>
<property name="MinimumRate" type="d" access="read"/>
<property name="MaximumRate" type="d" access="read"/>
<property name="CanGoNext" type="b" access="read"/>
<property name="CanGoPrevious" type="b" access="read"/>
<property name="CanPlay" type="b" access="read"/>
<property name="CanPause" type="b" access="read"/>
<property name="CanSeek" type="b" access="read"/>
<property name="CanControl" type="b" access="read"/>
</interface>
<interface name="org.mpris.MediaPlayer2.TrackList">
<method name="GetTracksMetadata">
<arg name="TrackIds" type="ao" direction="in"/>
<arg name="Metadata" type="aa{sv}" direction="out"/>
</method>
<method name="AddTrack">
<arg name="Uri" type="s" direction="in"/>
<arg name="AfterTrack" type="o" direction="in"/>
<arg name="SetAsCurrent" type="b" direction="in"/>
</method>
<method name="RemoveTrack">
<arg name="TrackId" type="o" direction="in"/>
</method>
<method name="GoTo">
<arg name="TrackId" type="o" direction="in"/>
</method>
<signal name="TrackListReplaced">
<arg name="Tracks" type="ao"/>
<arg name="CurrentTrack" type="o"/>
</signal>
<signal name="TrackAdded">
<arg name="Metadata" type="a{sv}"/>
<arg name="AfterTrack" type="o"/>
</signal>
<signal name="TrackRemoved">
<arg name="TrackId" type="o"/>
</signal>
<signal name="TrackMetadataChanged">
<arg name="TrackId" type="o"/>
<arg name="Metadata" type="a{sv}"/>
</signal>
<property name="Tracks" type="ao" access="read"/>
<property name="CanEditTracks" type="b" access="read"/>
</interface>
</node>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
#include <clapper/clapper-feature.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_MPRIS (clapper_mpris_get_type())
#define CLAPPER_MPRIS_CAST(obj) ((ClapperMpris *)(obj))
G_DECLARE_FINAL_TYPE (ClapperMpris, clapper_mpris, CLAPPER, MPRIS, ClapperFeature)
ClapperMpris * clapper_mpris_new (const gchar *own_name, const gchar *identity, const gchar *desktop_entry);
void clapper_mpris_set_queue_controllable (ClapperMpris *mpris, gboolean controllable);
gboolean clapper_mpris_get_queue_controllable (ClapperMpris *mpris);
void clapper_mpris_set_fallback_art_url (ClapperMpris *mpris, const gchar *art_url);
gchar * clapper_mpris_get_fallback_art_url (ClapperMpris *mpris);
G_END_DECLS

View File

@@ -0,0 +1,48 @@
feature_option = get_option(feature_name)
if feature_option.disabled()
subdir_done()
endif
# Known OSes that can support our MPRIS implementation
os_supported = ['linux'].contains(host_machine.system())
if not os_supported
if feature_option.enabled()
error('@0@ feature was enabled, but OS is not supported by it'.format(feature_name))
endif
subdir_done()
endif
feature_deps = [
dependency('gio-unix-2.0', version: glib_req, required: false),
]
foreach dep : feature_deps
if not dep.found()
if feature_option.enabled()
error('@0@ feature was enabled, but required dependencies were not found'.format(feature_name))
endif
subdir_done()
endif
endforeach
clapper_mpris_gdbus = gnome.gdbus_codegen('clapper-mpris-gdbus',
sources: 'clapper-mpris-gdbus.xml',
interface_prefix: 'org.mpris.',
namespace: 'ClapperMpris',
)
clapper_features_headers += [
'features/mpris/clapper-mpris.h',
]
clapper_features_sources += [
'features/mpris/clapper-mpris.c',
]
clapper_features_sources_internal += [
clapper_mpris_gdbus,
]
clapper_features_deps += feature_deps
install_headers('clapper-mpris.h',
install_dir: join_paths(clapper_headers_dir, 'features', 'mpris'),
)
clapper_available_features += feature_name

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include "clapper-enums.h"
G_BEGIN_DECLS
typedef enum
{
CLAPPER_SERVER_ACTION_INVALID = 0,
CLAPPER_SERVER_ACTION_TOGGLE_PLAY,
CLAPPER_SERVER_ACTION_PLAY,
CLAPPER_SERVER_ACTION_PAUSE,
CLAPPER_SERVER_ACTION_STOP,
CLAPPER_SERVER_ACTION_SEEK,
CLAPPER_SERVER_ACTION_SET_SPEED,
CLAPPER_SERVER_ACTION_SET_VOLUME,
CLAPPER_SERVER_ACTION_SET_MUTE,
CLAPPER_SERVER_ACTION_SET_PROGRESSION,
CLAPPER_SERVER_ACTION_ADD,
CLAPPER_SERVER_ACTION_INSERT,
CLAPPER_SERVER_ACTION_SELECT,
CLAPPER_SERVER_ACTION_REMOVE,
CLAPPER_SERVER_ACTION_CLEAR
} ClapperServerAction;
G_GNUC_INTERNAL
ClapperServerAction clapper_server_actions_get_action (const gchar *text);
G_GNUC_INTERNAL
gboolean clapper_server_actions_parse_seek (const gchar *text, gdouble *position);
G_GNUC_INTERNAL
gboolean clapper_server_actions_parse_set_speed (const gchar *text, gdouble *speed);
G_GNUC_INTERNAL
gboolean clapper_server_actions_parse_set_volume (const gchar *text, gdouble *volume);
G_GNUC_INTERNAL
gboolean clapper_server_actions_parse_set_mute (const gchar *text, gboolean *mute);
G_GNUC_INTERNAL
gboolean clapper_server_actions_parse_set_progression (const gchar *text, ClapperQueueProgressionMode *mode);
G_GNUC_INTERNAL
gboolean clapper_server_actions_parse_add (const gchar *text, const gchar **uri);
G_GNUC_INTERNAL
gboolean clapper_server_actions_parse_insert (const gchar *text, gchar **uri, guint *after_id);
G_GNUC_INTERNAL
gboolean clapper_server_actions_parse_select (const gchar *text, guint *id);
G_GNUC_INTERNAL
gboolean clapper_server_actions_parse_remove (const gchar *text, guint *id);
G_END_DECLS

View File

@@ -0,0 +1,234 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <gst/gst.h>
#include "clapper-server-actions-private.h"
#include "clapper-server-names-private.h"
#include "clapper-enums.h"
inline ClapperServerAction
clapper_server_actions_get_action (const gchar *text)
{
/* Actions without arg(s) */
if (strcmp (text, "toggle_play") == 0)
return CLAPPER_SERVER_ACTION_TOGGLE_PLAY;
if (strcmp (text, "play") == 0)
return CLAPPER_SERVER_ACTION_PLAY;
if (strcmp (text, "pause") == 0)
return CLAPPER_SERVER_ACTION_PAUSE;
if (strcmp (text, "stop") == 0)
return CLAPPER_SERVER_ACTION_STOP;
if (strcmp (text, "clear") == 0)
return CLAPPER_SERVER_ACTION_CLEAR;
/* Actions followed by space and arg(s) */
if (g_str_has_prefix (text, "seek "))
return CLAPPER_SERVER_ACTION_SEEK;
if (g_str_has_prefix (text, "set_speed "))
return CLAPPER_SERVER_ACTION_SET_SPEED;
if (g_str_has_prefix (text, "set_volume "))
return CLAPPER_SERVER_ACTION_SET_VOLUME;
if (g_str_has_prefix (text, "set_mute "))
return CLAPPER_SERVER_ACTION_SET_MUTE;
if (g_str_has_prefix (text, "set_progression "))
return CLAPPER_SERVER_ACTION_SET_PROGRESSION;
if (g_str_has_prefix (text, "add "))
return CLAPPER_SERVER_ACTION_ADD;
if (g_str_has_prefix (text, "insert "))
return CLAPPER_SERVER_ACTION_INSERT;
if (g_str_has_prefix (text, "select "))
return CLAPPER_SERVER_ACTION_SELECT;
if (g_str_has_prefix (text, "remove "))
return CLAPPER_SERVER_ACTION_REMOVE;
return CLAPPER_SERVER_ACTION_INVALID;
}
static inline gboolean
_string_is_number (const gchar *string, gboolean decimal)
{
guint i;
for (i = 0; string[i] != '\0'; ++i) {
if (!g_ascii_isdigit (string[i])) {
if (decimal && string[i] == '.')
continue;
return FALSE;
}
}
return (i > 0);
}
static gboolean
_parse_uint (const gchar *text, guint *val)
{
gint64 tmp_val;
if (!_string_is_number (text, FALSE))
return FALSE;
tmp_val = g_ascii_strtoll (text, NULL, 10);
/* guint overflow check */
if (tmp_val < 0 || tmp_val > G_MAXUINT)
return FALSE;
*val = (guint) tmp_val;
return TRUE;
}
static gboolean
_parse_double (const gchar *text, gdouble *val)
{
if (!_string_is_number (text, TRUE))
return FALSE;
*val = g_ascii_strtod (text, NULL);
return TRUE;
}
static gboolean
_parse_boolean (const gchar *text, gboolean *val)
{
gboolean res;
if ((res = (strcmp (text, "true") == 0)))
*val = TRUE;
else if ((res = (strcmp (text, "false") == 0)))
*val = FALSE;
return res;
}
inline gboolean
clapper_server_actions_parse_seek (const gchar *text, gdouble *position)
{
/* "seek" + whitespace = 5 */
if (!_parse_double (text + 5, position))
return FALSE;
return (*position >= 0);
}
inline gboolean
clapper_server_actions_parse_set_speed (const gchar *text, gdouble *speed)
{
/* "set_speed" + whitespace = 10 */
return _parse_double (text + 10, speed);
}
inline gboolean
clapper_server_actions_parse_set_volume (const gchar *text, gdouble *volume)
{
/* "set_volume" + whitespace = 11 */
if (!_parse_double (text + 11, volume))
return FALSE;
if (*volume <= 0 || *volume > 2.0)
return FALSE;
return TRUE;
}
inline gboolean
clapper_server_actions_parse_set_mute (const gchar *text, gboolean *mute)
{
/* "set_mute" + whitespace = 9 */
return _parse_boolean (text + 9, mute);
}
inline gboolean
clapper_server_actions_parse_set_progression (const gchar *text, ClapperQueueProgressionMode *mode)
{
gboolean res;
/* "set_progression" + whitespace = 16 */
text += 16;
if ((res = (strcmp (text, CLAPPER_SERVER_QUEUE_PROGRESSION_NONE) == 0)))
*mode = CLAPPER_QUEUE_PROGRESSION_NONE;
else if ((res = (strcmp (text, CLAPPER_SERVER_QUEUE_PROGRESSION_CONSECUTIVE) == 0)))
*mode = CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE;
else if ((res = (strcmp (text, CLAPPER_SERVER_QUEUE_PROGRESSION_REPEAT_ITEM) == 0)))
*mode = CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM;
else if ((res = (strcmp (text, CLAPPER_SERVER_QUEUE_PROGRESSION_CAROUSEL) == 0)))
*mode = CLAPPER_QUEUE_PROGRESSION_CAROUSEL;
else if ((res = (strcmp (text, CLAPPER_SERVER_QUEUE_PROGRESSION_SHUFFLE) == 0)))
*mode = CLAPPER_QUEUE_PROGRESSION_SHUFFLE;
return res;
}
inline gboolean
clapper_server_actions_parse_add (const gchar *text, const gchar **uri)
{
/* "add" + whitespace = 4 */
text += 4;
/* No more spaces allowed */
if (strchr (text, ' ') != NULL)
return FALSE;
if (!gst_uri_is_valid (text))
return FALSE;
*uri = text;
return TRUE;
}
inline gboolean
clapper_server_actions_parse_insert (const gchar *text, gchar **uri, guint *after_id)
{
gchar **data;
gboolean res;
/* "insert" + whitespace = 7 */
text += 7;
data = g_strsplit (text, " ", 2);
if ((res = (g_strv_length (data) == 2
&& gst_uri_is_valid (data[0])
&& _parse_uint (data[1], after_id)))) {
*uri = g_strdup (data[0]);
}
g_strfreev (data);
return res;
}
inline gboolean
clapper_server_actions_parse_select (const gchar *text, guint *id)
{
/* "select" + whitespace = 7 */
return _parse_uint (text + 7, id);
}
inline gboolean
clapper_server_actions_parse_remove (const gchar *text, guint *id)
{
/* "remove" + whitespace = 7 */
return _parse_uint (text + 7, id);
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include "clapper-server.h"
#include "clapper-media-item.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
gchar * clapper_server_json_build_complete (ClapperServer *server, ClapperMediaItem *played_item, guint played_index, GPtrArray *items);
G_END_DECLS

View File

@@ -0,0 +1,218 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "clapper-server-json-private.h"
#include "clapper-server-names-private.h"
#include "clapper-player.h"
#include "clapper-queue.h"
#define CLAPPER_SERVER_JSON_BUILD(dest, ...) { \
GString *_json = g_string_new ("{"); \
__VA_ARGS__ \
g_string_append (_json, "}"); \
*dest = g_string_free (_json, FALSE); }
#define _JSON_AUTO_COMMA \
if (_json->str[_json->len - 1] != '{' \
&& _json->str[_json->len - 1] != '[') \
g_string_append (_json, ",");
#define _ADD_KEY_VAL_BOOLEAN(key, val) \
_JSON_AUTO_COMMA \
g_string_append_printf (_json, "\"%s\":%s", key, (val) ? "true" : "false");
#define _ADD_KEY_VAL_UINT(key, val) \
_JSON_AUTO_COMMA \
g_string_append_printf (_json, "\"%s\":%" G_GUINT64_FORMAT, key, (guint64) val);
#define _ADD_KEY_VAL_DOUBLE(key, val) \
_JSON_AUTO_COMMA \
g_string_append_printf (_json, "\"%s\":%.2lf", key, (gdouble) val);
#define _ADD_KEY_VAL_STRING(key, val) \
_JSON_AUTO_COMMA \
if (G_UNLIKELY (val == NULL)) \
g_string_append_printf (_json, "\"%s\":null", key); \
else \
g_string_append_printf (_json, "\"%s\":\"%s\"", key, val);
#define _ADD_KEY_VAL_STRING_TAKE(key, val) \
_ADD_KEY_VAL_STRING(key, val) \
g_free (val);
#define _ADD_VAL_STRING(val) \
_JSON_AUTO_COMMA \
if (G_UNLIKELY (val == NULL)) \
g_string_append (_json, "null"); \
else \
g_string_append_printf (_json, "\"%s\"", val);
#define _ADD_VAL_STRING_TAKE(val) \
_ADD_VAL_STRING(val) \
g_free (val);
#define _ADD_OBJECT(...) \
_JSON_AUTO_COMMA \
g_string_append (_json, "{"); \
__VA_ARGS__ \
g_string_append (_json, "}");
#define _ADD_NAMED_OBJECT(name, ...) \
_JSON_AUTO_COMMA \
g_string_append_printf (_json, "\"%s\":{", name); \
__VA_ARGS__ \
g_string_append (_json, "}");
#define _ADD_NAMED_ARRAY(name, ...) \
_JSON_AUTO_COMMA \
g_string_append_printf (_json, "\"%s\":[", name); \
__VA_ARGS__ \
g_string_append (_json, "]");
static inline void
clapper_server_json_escape_string (gchar **string)
{
gchar *dest, *src = *string;
guint i, offset = 0;
for (i = 0; src[i] != '\0'; ++i) {
switch (src[i]) {
case '\"':
offset++;
default:
break;
}
}
/* Nothing to escape, leave string unchaged */
if (offset == 0)
return;
/* Previous length + n_escapes + term */
dest = g_new (gchar, i + offset + 1);
offset = 0;
for (i = 0; src[i] != '\0'; ++i) {
switch (src[i]) {
case '\"':
dest[i + offset] = '\\';
dest[i + offset + 1] = '\"';
offset++;
break;
default:
dest[i + offset] = src[i];
break;
}
}
dest[i + offset] = '\0';
g_free (*string);
*string = dest;
}
gchar *
clapper_server_json_build_complete (ClapperServer *server, ClapperMediaItem *played_item,
guint played_index, GPtrArray *items)
{
ClapperPlayer *player;
gchar *data = NULL;
player = CLAPPER_PLAYER_CAST (gst_object_get_parent (GST_OBJECT_CAST (server)));
if (G_UNLIKELY (player == NULL))
return NULL;
CLAPPER_SERVER_JSON_BUILD (&data, {
switch (clapper_player_get_state (player)) {
case CLAPPER_PLAYER_STATE_PLAYING:
_ADD_KEY_VAL_STRING ("state", CLAPPER_SERVER_PLAYER_STATE_PLAYING);
break;
case CLAPPER_PLAYER_STATE_PAUSED:
_ADD_KEY_VAL_STRING ("state", CLAPPER_SERVER_PLAYER_STATE_PAUSED);
break;
case CLAPPER_PLAYER_STATE_BUFFERING:
_ADD_KEY_VAL_STRING ("state", CLAPPER_SERVER_PLAYER_STATE_BUFFERING);
break;
case CLAPPER_PLAYER_STATE_STOPPED:
_ADD_KEY_VAL_STRING ("state", CLAPPER_SERVER_PLAYER_STATE_STOPPED);
break;
default:
g_assert_not_reached ();
break;
}
_ADD_KEY_VAL_UINT ("position", clapper_player_get_position (player));
_ADD_KEY_VAL_DOUBLE ("speed", clapper_player_get_speed (player));
_ADD_KEY_VAL_DOUBLE ("volume", clapper_player_get_volume (player));
_ADD_KEY_VAL_BOOLEAN ("mute", clapper_player_get_mute (player));
_ADD_NAMED_OBJECT ("queue", {
ClapperQueue *queue = clapper_player_get_queue (player);
_ADD_KEY_VAL_BOOLEAN ("controllable", clapper_server_get_queue_controllable (server));
_ADD_KEY_VAL_UINT ("played_index", played_index);
_ADD_KEY_VAL_UINT ("n_items", items->len);
switch (clapper_queue_get_progression_mode (queue)) {
case CLAPPER_QUEUE_PROGRESSION_NONE:
_ADD_KEY_VAL_STRING ("progression", CLAPPER_SERVER_QUEUE_PROGRESSION_NONE);
break;
case CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE:
_ADD_KEY_VAL_STRING ("progression", CLAPPER_SERVER_QUEUE_PROGRESSION_CONSECUTIVE);
break;
case CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM:
_ADD_KEY_VAL_STRING ("progression", CLAPPER_SERVER_QUEUE_PROGRESSION_REPEAT_ITEM);
break;
case CLAPPER_QUEUE_PROGRESSION_CAROUSEL:
_ADD_KEY_VAL_STRING ("progression", CLAPPER_SERVER_QUEUE_PROGRESSION_CAROUSEL);
break;
case CLAPPER_QUEUE_PROGRESSION_SHUFFLE:
_ADD_KEY_VAL_STRING ("progression", CLAPPER_SERVER_QUEUE_PROGRESSION_SHUFFLE);
break;
default:
g_assert_not_reached ();
break;
}
_ADD_NAMED_ARRAY ("items", {
guint i;
for (i = 0; i < items->len; ++i) {
_ADD_OBJECT ({
ClapperMediaItem *item = (ClapperMediaItem *) g_ptr_array_index (items, i);
gchar *title = clapper_media_item_get_title (item);
if (title)
clapper_server_json_escape_string (&title);
_ADD_KEY_VAL_UINT ("id", clapper_media_item_get_id (item));
_ADD_KEY_VAL_STRING_TAKE ("title", title);
_ADD_KEY_VAL_UINT ("duration", clapper_media_item_get_duration (item));
/* TODO: Add more info per item (including timeline markers) */
});
}
});
});
});
gst_object_unref (player);
return data;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <microdns/microdns.h>
G_BEGIN_DECLS
G_GNUC_INTERNAL
void clapper_server_mdns_debug_init (void);
G_GNUC_INTERNAL
void clapper_server_mdns_serve (gchar *name, guint port);
G_GNUC_INTERNAL
void clapper_server_mdns_remove (guint port);
G_END_DECLS

View File

@@ -0,0 +1,351 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <gst/gst.h>
#include "clapper-server-mdns-private.h"
#define CLAPPER_SERVER_MDNS_SERVICE "_clapper._tcp.local"
#define N_RESP 4
#define PTR_INDEX(i) (i * N_RESP)
#define TXT_INDEX(i) (PTR_INDEX(i) + 1)
#define SRV_INDEX(i) (PTR_INDEX(i) + 2)
#define A_AAAA_INDEX(i) (PTR_INDEX(i) + 3)
typedef struct
{
gchar *name;
gchar *service_link;
guint port;
} ClapperServerMdnsEntry;
typedef struct
{
GPtrArray *entries;
GPtrArray *pending_entries;
} ClapperServerMdns;
#define GST_CAT_DEFAULT clapper_server_mdns_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
static ClapperServerMdns *mdns = NULL;
static GCond mdns_cond;
static GMutex mdns_lock;
void
clapper_server_mdns_debug_init (void)
{
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperservermdns",
GST_DEBUG_FG_RED, "Clapper Server MDNS");
}
static void
clapper_server_mdns_entry_free (ClapperServerMdnsEntry *entry)
{
GST_TRACE ("Freeing MDNS entry: %p", entry);
g_free (entry->name);
g_free (entry->service_link);
g_free (entry);
}
static void
clapper_server_mdns_remove_port (GPtrArray *entries, guint port)
{
guint i;
for (i = 0; i < entries->len; ++i) {
ClapperServerMdnsEntry *entry = g_ptr_array_index (entries, i);
if (entry->port == port) {
GST_TRACE ("Removing entry with port: %u", port);
g_ptr_array_remove_index (entries, i);
break;
}
}
}
static inline void
_send_entries (struct mdns_ctx *ctx, const struct sockaddr *addr,
enum mdns_announce_type type, GPtrArray *entries)
{
const guint n_answers = N_RESP * entries->len;
gchar domain_name[32];
guint i;
struct rr_entry *answers = g_alloca0 (sizeof (struct rr_entry) * n_answers);
struct mdns_hdr hdr = { 0, };
hdr.flags |= FLAG_QR;
hdr.flags |= FLAG_AA;
hdr.num_ans_rr = n_answers;
g_snprintf (domain_name, sizeof (domain_name), "%s.local", g_get_host_name ());
for (i = 0; i < entries->len; ++i) {
ClapperServerMdnsEntry *entry = g_ptr_array_index (entries, i);
GST_LOG ("Preparing answers for MDNS query, service: \"%s\""
", domain: \"%s\", link: \"%s\"",
CLAPPER_SERVER_MDNS_SERVICE, domain_name, entry->service_link);
answers[PTR_INDEX(i)] = (struct rr_entry) {
.type = RR_PTR,
.name = (char *) CLAPPER_SERVER_MDNS_SERVICE,
.data.PTR.domain = entry->service_link,
.rr_class = RR_IN,
.msbit = 1,
.ttl = (type == MDNS_ANNOUNCE_GOODBYE) ? 0 : 120,
.next = &answers[TXT_INDEX(i)]
};
answers[TXT_INDEX(i)] = (struct rr_entry) {
.type = RR_TXT,
.name = entry->service_link,
.rr_class = RR_IN,
.msbit = 1,
.ttl = (type == MDNS_ANNOUNCE_GOODBYE) ? 0 : 120,
.next = &answers[SRV_INDEX(i)]
};
answers[SRV_INDEX(i)] = (struct rr_entry) {
.type = RR_SRV,
.name = entry->service_link,
.data.SRV.port = entry->port,
.data.SRV.priority = 0,
.data.SRV.weight = 0,
.data.SRV.target = domain_name,
.rr_class = RR_IN,
.msbit = 1,
.ttl = (type == MDNS_ANNOUNCE_GOODBYE) ? 0 : 120,
.next = &answers[A_AAAA_INDEX(i)]
};
answers[A_AAAA_INDEX(i)] = (struct rr_entry) {
.name = domain_name,
.rr_class = RR_IN,
.msbit = 1,
.ttl = (type == MDNS_ANNOUNCE_GOODBYE) ? 0 : 120,
.next = (i + 1 < entries->len) ? &answers[PTR_INDEX(i + 1)] : NULL
};
if (addr->sa_family == AF_INET) {
answers[A_AAAA_INDEX(i)].type = RR_A;
memcpy (&answers[A_AAAA_INDEX(i)].data.A.addr,
&((struct sockaddr_in *) addr)->sin_addr,
sizeof (answers[A_AAAA_INDEX(i)].data.A.addr));
} else {
answers[A_AAAA_INDEX(i)].type = RR_AAAA;
memcpy(&answers[A_AAAA_INDEX(i)].data.AAAA.addr,
&((struct sockaddr_in6 *) addr)->sin6_addr,
sizeof (answers[A_AAAA_INDEX(i)].data.AAAA.addr));
}
GST_LOG ("Prepared %u/%u bunches of answers", i + 1, entries->len);
}
/* Needs to still have lock here, as pointers
* in answers are simply assigned from entries */
GST_LOG ("Sending all answers");
mdns_entries_send (ctx, &hdr, answers);
}
static void
_mdns_cb (struct mdns_ctx *ctx, const struct sockaddr *addr,
const char* service, enum mdns_announce_type type)
{
if (service && strcmp (service, CLAPPER_SERVER_MDNS_SERVICE) != 0)
return;
g_mutex_lock (&mdns_lock);
switch (type) {
case MDNS_ANNOUNCE_INITIAL:
if (mdns->pending_entries->len > 0) {
GST_LOG ("Handling announcement type: INITIAL");
_send_entries (ctx, addr, type, mdns->pending_entries);
/* Move to entries after initial announcement */
while (mdns->pending_entries->len > 0) {
ClapperServerMdnsEntry *entry;
/* MDNS advertises entries in reverse order */
entry = g_ptr_array_steal_index (mdns->pending_entries, 0);
g_ptr_array_insert (mdns->entries, 0, entry);
}
}
break;
case MDNS_ANNOUNCE_RESPONSE:
case MDNS_ANNOUNCE_GOODBYE:
if (mdns->entries->len > 0) {
GST_LOG ("Handling announcement type: %s",
(type == MDNS_ANNOUNCE_RESPONSE) ? "RESPONSE" : "GOODBYE");
_send_entries (ctx, addr, type, mdns->entries);
}
break;
default:
break;
}
g_mutex_unlock (&mdns_lock);
}
static gboolean
mdns_stop_cb (struct mdns_ctx *ctx)
{
gboolean announce;
g_mutex_lock (&mdns_lock);
if (mdns->entries->len == 0
&& mdns->pending_entries->len == 0) {
g_mutex_unlock (&mdns_lock);
return TRUE;
}
announce = (mdns->pending_entries->len > 0);
g_mutex_unlock (&mdns_lock);
if (announce)
mdns_request_initial_announce (ctx, NULL);
return FALSE;
}
static gpointer
mdns_thread_func (gpointer user_data)
{
struct mdns_ctx *ctx = NULL;
int resp;
char err_str[128];
GST_TRACE ("MDNS init");
if ((resp = mdns_init (&ctx, MDNS_ADDR_IPV4, MDNS_PORT)) < 0) {
mdns_strerror(resp, err_str, sizeof (err_str));
GST_ERROR ("Could not initialize MDNS, reason: %s", err_str);
return NULL;
}
mdns_announce (ctx, RR_PTR, (mdns_announce_callback) _mdns_cb, ctx);
GST_DEBUG ("MDNS start");
serve:
if ((resp = mdns_serve (ctx, (mdns_stop_func) mdns_stop_cb, ctx)) < 0) {
mdns_strerror(resp, err_str, sizeof (err_str));
GST_ERROR ("Could start MDNS, reason: %s", err_str);
}
g_mutex_lock (&mdns_lock);
/* Can happen when stopped due to lack of entries,
* but entries were added afterwards */
if (resp >= 0 && (mdns->entries->len > 0
|| mdns->pending_entries->len > 0)) {
g_mutex_unlock (&mdns_lock);
goto serve;
}
/* No more going back now */
GST_DEBUG ("MDNS stop");
/* Destroy with a lock, this ensures unbind
* of MDNS_PORT before doing "mdns_init" again */
GST_TRACE ("MDNS destroy");
mdns_destroy (ctx);
GST_TRACE ("Freeing MDNS entries storage: %p", mdns);
g_ptr_array_unref (mdns->entries);
g_ptr_array_unref (mdns->pending_entries);
g_clear_pointer (&mdns, g_free);
g_cond_broadcast (&mdns_cond);
g_mutex_unlock (&mdns_lock);
return NULL;
}
void
clapper_server_mdns_serve (gchar *name, guint port)
{
ClapperServerMdnsEntry *entry;
const gchar *prgname = g_get_prgname ();
gboolean stopped;
entry = g_new (ClapperServerMdnsEntry, 1);
entry->name = name;
entry->service_link = g_strdup_printf ("%s %s %s.%s",
g_get_host_name (), (prgname) ? prgname : "clapperplayer",
entry->name, CLAPPER_SERVER_MDNS_SERVICE);
entry->port = port;
GST_TRACE ("Created MDNS entry: %p", entry);
g_mutex_lock (&mdns_lock);
if ((stopped = mdns == NULL)) {
mdns = g_new (ClapperServerMdns, 1);
mdns->entries = g_ptr_array_new_with_free_func (
(GDestroyNotify) clapper_server_mdns_entry_free);
mdns->pending_entries = g_ptr_array_new_with_free_func (
(GDestroyNotify) clapper_server_mdns_entry_free);
GST_TRACE ("Created MDNS entries storage: %p", mdns);
}
g_ptr_array_add (mdns->pending_entries, entry);
g_mutex_unlock (&mdns_lock);
if (stopped) {
GThread *thread;
GError *error = NULL;
GST_DEBUG ("Starting MDNS service");
thread = g_thread_try_new ("clapper-server-mdns",
(GThreadFunc) mdns_thread_func, NULL, &error);
if (error) {
GST_ERROR ("Could not create MDNS thread, reason: %s", error->message);
g_error_free (error);
} else {
g_thread_unref (thread);
}
}
}
void
clapper_server_mdns_remove (guint port)
{
g_mutex_lock (&mdns_lock);
clapper_server_mdns_remove_port (mdns->entries, port);
clapper_server_mdns_remove_port (mdns->pending_entries, port);
if (mdns
&& mdns->entries->len == 0
&& mdns->pending_entries->len == 0) {
GST_DEBUG ("MDNS is going to stop");
while (mdns != NULL)
g_cond_wait (&mdns_cond, &mdns_lock);
}
g_mutex_unlock (&mdns_lock);
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
G_BEGIN_DECLS
#define CLAPPER_SERVER_WS_EVENT_STATE "state"
#define CLAPPER_SERVER_WS_EVENT_POSITION "position"
#define CLAPPER_SERVER_WS_EVENT_SPEED "speed"
#define CLAPPER_SERVER_WS_EVENT_VOLUME "volume"
#define CLAPPER_SERVER_WS_EVENT_MUTED "muted"
#define CLAPPER_SERVER_WS_EVENT_UNMUTED "unmuted"
#define CLAPPER_SERVER_WS_EVENT_PLAYED_INDEX "played_index"
#define CLAPPER_SERVER_WS_EVENT_QUEUE_CHANGED "queue_changed"
#define CLAPPER_SERVER_WS_EVENT_QUEUE_PROGRESSION "queue_progression"
#define CLAPPER_SERVER_PLAYER_STATE_STOPPED "stopped"
#define CLAPPER_SERVER_PLAYER_STATE_BUFFERING "buffering"
#define CLAPPER_SERVER_PLAYER_STATE_PAUSED "paused"
#define CLAPPER_SERVER_PLAYER_STATE_PLAYING "playing"
#define CLAPPER_SERVER_QUEUE_PROGRESSION_NONE "none"
#define CLAPPER_SERVER_QUEUE_PROGRESSION_CONSECUTIVE "consecutive"
#define CLAPPER_SERVER_QUEUE_PROGRESSION_REPEAT_ITEM "repeat_item"
#define CLAPPER_SERVER_QUEUE_PROGRESSION_CAROUSEL "carousel"
#define CLAPPER_SERVER_QUEUE_PROGRESSION_SHUFFLE "shuffle"
G_END_DECLS

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <clapper/clapper-feature.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_SERVER (clapper_server_get_type())
#define CLAPPER_SERVER_CAST(obj) ((ClapperServer *)(obj))
G_DECLARE_FINAL_TYPE (ClapperServer, clapper_server, CLAPPER, SERVER, ClapperFeature)
ClapperServer * clapper_server_new (void);
void clapper_server_set_enabled (ClapperServer *server, gboolean enabled);
gboolean clapper_server_get_enabled (ClapperServer *server);
gboolean clapper_server_get_running (ClapperServer *server);
void clapper_server_set_port (ClapperServer *server, guint port);
guint clapper_server_get_port (ClapperServer *server);
guint clapper_server_get_current_port (ClapperServer *server);
void clapper_server_set_queue_controllable (ClapperServer *server, gboolean controllable);
gboolean clapper_server_get_queue_controllable (ClapperServer *server);
G_END_DECLS

View File

@@ -0,0 +1,34 @@
feature_option = get_option(feature_name)
if feature_option.disabled()
subdir_done()
endif
feature_deps = [
dependency('libsoup-3.0', required: false),
dependency('microdns', version: '>= 0.2.0', required: false),
]
foreach dep : feature_deps
if not dep.found()
if feature_option.enabled()
error('@0@ feature was enabled, but required dependencies were not found'.format(feature_name))
endif
subdir_done()
endif
endforeach
clapper_features_headers += [
'features/server/clapper-server.h',
]
clapper_features_sources += [
'features/server/clapper-server.c',
'features/server/clapper-server-json.c',
'features/server/clapper-server-actions.c',
'features/server/clapper-server-mdns.c',
]
clapper_features_deps += feature_deps
install_headers('clapper-server.h',
install_dir: join_paths(clapper_headers_dir, 'features', 'server'),
)
clapper_available_features += feature_name

219
src/lib/clapper/meson.build Normal file
View File

@@ -0,0 +1,219 @@
clapper_dep = dependency('', required: false)
clapper_option = get_option('clapper')
build_clapper = false
clapper_pkg_reqs = [
'gstreamer-1.0',
'gstreamer-base-1.0',
'gstreamer-audio-1.0',
'gstreamer-tag-1.0',
'gstreamer-pbutils-1.0',
'glib-2.0',
'gobject-2.0',
'gio-2.0',
]
if clapper_option.disabled()
subdir_done()
endif
clapper_deps = [
gst_dep,
gst_base_dep,
gst_audio_dep,
gst_tag_dep,
gst_pbutils_dep,
glib_dep,
gobject_dep,
gio_dep,
]
foreach dep : clapper_deps
if not dep.found()
if clapper_option.enabled()
error('clapper option was enabled, but required dependencies were not found')
endif
subdir_done()
endif
endforeach
version_conf = configuration_data()
version_conf.set(
'CLAPPER_VERSION',
meson.project_version(),
)
version_conf.set(
'CLAPPER_MAJOR_VERSION',
version_array[0].to_int(),
)
version_conf.set(
'CLAPPER_MINOR_VERSION',
version_array[1].to_int(),
)
version_conf.set(
'CLAPPER_MICRO_VERSION',
version_array[2].to_int(),
)
clapper_headers_dir = join_paths(includedir, clapper_api_name, 'clapper')
clapper_version_header = configure_file(
input: 'clapper-version.h.in',
output: 'clapper-version.h',
configuration: version_conf,
)
# Include the generated headers
clapper_conf_inc = [
include_directories('.'),
include_directories('..'),
]
clapper_headers = [
'clapper.h',
'clapper-enums.h',
'clapper-audio-stream.h',
'clapper-feature.h',
'clapper-marker.h',
'clapper-media-item.h',
'clapper-player.h',
'clapper-queue.h',
'clapper-stream.h',
'clapper-stream-list.h',
'clapper-subtitle-stream.h',
'clapper-threaded-object.h',
'clapper-timeline.h',
'clapper-utils.h',
'clapper-video-stream.h',
clapper_version_header,
]
clapper_sources = [
'clapper.c',
'clapper-app-bus.c',
'clapper-audio-stream.c',
'clapper-feature.c',
'clapper-features-bus.c',
'clapper-features-manager.c',
'clapper-marker.c',
'clapper-media-item.c',
'clapper-playbin-bus.c',
'clapper-player.c',
'clapper-queue.c',
'clapper-stream.c',
'clapper-stream-list.c',
'clapper-subtitle-stream.c',
'clapper-threaded-object.c',
'clapper-timeline.c',
'clapper-utils.c',
'clapper-video-stream.c',
'../shared/clapper-shared-utils.c',
]
clapper_c_args = [
'-DG_LOG_DOMAIN="Clapper"',
'-DCLAPPER_COMPILATION',
'-DGST_USE_UNSTABLE_API',
]
subdir('features')
clapper_enums = gnome.mkenums_simple(
'clapper-enum-types',
sources: clapper_headers,
identifier_prefix: 'Clapper',
symbol_prefix: 'clapper',
install_header: true,
install_dir: clapper_headers_dir,
)
clapper_lib = library(
clapper_api_name,
clapper_sources + clapper_features_sources + clapper_features_sources_internal + clapper_enums,
dependencies: clapper_deps + clapper_features_deps,
include_directories: clapper_conf_inc,
c_args: clapper_c_args,
version: clapper_version,
install: true,
)
install_headers(clapper_headers,
install_dir: clapper_headers_dir,
)
build_clapper = true
if build_gir
clapper_gir = gnome.generate_gir(clapper_lib,
sources: [
clapper_sources,
clapper_features_sources,
clapper_headers,
clapper_features_headers,
clapper_enums,
],
extra_args: [
gir_init_section,
'--quiet',
'--warn-all',
'-DCLAPPER_COMPILATION',
'-DGST_USE_UNSTABLE_API',
],
nsversion: version_array[0] + '.0',
namespace: 'Clapper',
identifier_prefix: 'Clapper',
symbol_prefix: 'clapper',
export_packages: clapper_api_name,
install: true,
includes: [
'Gst-1.0',
'GstBase-1.0',
'GstAudio-1.0',
'GstTag-1.0',
'GstPbutils-1.0',
'GLib-2.0',
'GObject-2.0',
'Gio-2.0',
],
header: join_paths(meson.project_name(), 'clapper.h'),
)
endif
if build_vapi
if not build_gir
if get_option('vapi').enabled()
error('Cannot build "vapi" without "introspection"')
endif
else
clapper_vapi = gnome.generate_vapi(clapper_api_name,
sources: clapper_gir[0],
packages: clapper_pkg_reqs,
metadata_dirs: [
join_paths (meson.current_source_dir(), 'metadata')
],
install: true,
)
endif
endif
clapper_pkgconfig_variables = [
'features=' + ' '.join(clapper_available_features),
]
pkgconfig.generate(clapper_lib,
unescaped_variables: clapper_pkgconfig_variables,
subdirs: [clapper_api_name],
filebase: clapper_api_name,
name: meson.project_name(),
version: meson.project_version(),
description: 'Clapper playback library',
requires: clapper_pkg_reqs,
)
clapper_dep = declare_dependency(
link_with: clapper_lib,
include_directories: clapper_conf_inc,
dependencies: clapper_deps + clapper_features_deps,
sources: [
clapper_version_header,
clapper_features_availability_header,
clapper_enums[1],
],
)

View File

@@ -0,0 +1,7 @@
// Skipped by GI, but Vala can handle it fine
//init_get_option_group skip=false
*_FORMAT skip=false
// Init func compatibility
init.argv unowned
init_check.argv unowned

1
src/lib/gst/meson.build Normal file
View File

@@ -0,0 +1 @@
subdir('plugin')

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstclappercontexthandler.h"
#define parent_class gst_clapper_context_handler_parent_class
G_DEFINE_TYPE (GstClapperContextHandler, gst_clapper_context_handler, GST_TYPE_OBJECT);
static gboolean
_default_handle_context_query (GstClapperContextHandler *self,
GstBaseSink *bsink, GstQuery *query)
{
GST_FIXME_OBJECT (self, "Need to handle context query");
return FALSE;
}
static void
gst_clapper_context_handler_init (GstClapperContextHandler *self)
{
}
static void
gst_clapper_context_handler_finalize (GObject *object)
{
GstClapperContextHandler *self = GST_CLAPPER_CONTEXT_HANDLER_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
gst_clapper_context_handler_class_init (GstClapperContextHandlerClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstClapperContextHandlerClass *handler_class = (GstClapperContextHandlerClass *) klass;
gobject_class->finalize = gst_clapper_context_handler_finalize;
handler_class->handle_context_query = _default_handle_context_query;
}
gboolean
gst_clapper_context_handler_handle_context_query (GstClapperContextHandler *self,
GstBaseSink *bsink, GstQuery *query)
{
GstClapperContextHandlerClass *handler_class = GST_CLAPPER_CONTEXT_HANDLER_GET_CLASS (self);
return handler_class->handle_context_query (self, bsink, query);
}
GstClapperContextHandler *
gst_clapper_context_handler_obtain_with_type (GPtrArray *context_handlers, GType type)
{
guint i;
for (i = 0; i < context_handlers->len; i++) {
GstClapperContextHandler *handler = g_ptr_array_index (context_handlers, i);
if (G_TYPE_CHECK_INSTANCE_TYPE (handler, type))
return gst_object_ref (handler);
}
return NULL;
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <gst/base/gstbasesink.h>
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_CONTEXT_HANDLER (gst_clapper_context_handler_get_type())
#define GST_IS_CLAPPER_CONTEXT_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_CONTEXT_HANDLER))
#define GST_IS_CLAPPER_CONTEXT_HANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_CONTEXT_HANDLER))
#define GST_CLAPPER_CONTEXT_HANDLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_CONTEXT_HANDLER, GstClapperContextHandlerClass))
#define GST_CLAPPER_CONTEXT_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_CONTEXT_HANDLER, GstClapperContextHandler))
#define GST_CLAPPER_CONTEXT_HANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_CONTEXT_HANDLER, GstClapperContextHandlerClass))
#define GST_CLAPPER_CONTEXT_HANDLER_CAST(obj) ((GstClapperContextHandler *)(obj))
typedef struct _GstClapperContextHandler GstClapperContextHandler;
typedef struct _GstClapperContextHandlerClass GstClapperContextHandlerClass;
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperContextHandler, gst_object_unref)
#endif
struct _GstClapperContextHandler
{
GstObject parent;
};
struct _GstClapperContextHandlerClass
{
GstObjectClass parent_class;
gboolean (* handle_context_query) (GstClapperContextHandler *handler,
GstBaseSink *bsink,
GstQuery *query);
};
GType gst_clapper_context_handler_get_type (void);
gboolean gst_clapper_context_handler_handle_context_query (GstClapperContextHandler *handler, GstBaseSink *bsink, GstQuery *query);
GstClapperContextHandler * gst_clapper_context_handler_obtain_with_type (GPtrArray *context_handlers, GType type);
G_END_DECLS

View File

@@ -0,0 +1,427 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstclapperimporter.h"
#include "gstgtkutils.h"
#define GST_CAT_DEFAULT gst_clapper_importer_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define parent_class gst_clapper_importer_parent_class
G_DEFINE_TYPE (GstClapperImporter, gst_clapper_importer, GST_TYPE_OBJECT);
typedef struct
{
GdkTexture *texture;
GstVideoOverlayRectangle *rectangle;
gint x, y;
guint width, height;
gint index;
gatomicrefcount ref_count;
} GstClapperGdkOverlay;
static GstClapperGdkOverlay *
gst_clapper_gdk_overlay_new (GdkTexture *texture, GstVideoOverlayRectangle *rectangle,
gint x, gint y, guint width, guint height, guint index)
{
GstClapperGdkOverlay *overlay = g_slice_new (GstClapperGdkOverlay);
overlay->texture = g_object_ref (texture);
overlay->rectangle = gst_video_overlay_rectangle_ref (rectangle);
overlay->x = x;
overlay->y = y;
overlay->width = width;
overlay->height = height;
overlay->index = index;
g_atomic_ref_count_init (&overlay->ref_count);
return overlay;
}
static GstClapperGdkOverlay *
gst_clapper_gdk_overlay_ref (GstClapperGdkOverlay *overlay)
{
g_atomic_ref_count_inc (&overlay->ref_count);
return overlay;
}
static void
gst_clapper_gdk_overlay_unref (GstClapperGdkOverlay *overlay)
{
if (g_atomic_ref_count_dec (&overlay->ref_count)) {
GST_TRACE ("Freeing overlay: %" GST_PTR_FORMAT, overlay);
g_object_unref (overlay->texture);
gst_video_overlay_rectangle_unref (overlay->rectangle);
g_slice_free (GstClapperGdkOverlay, overlay);
}
}
static GstBufferPool *
_default_create_pool (GstClapperImporter *self, GstStructure **config)
{
GST_FIXME_OBJECT (self, "Need to create buffer pool");
return NULL;
}
static void
_default_add_allocation_metas (GstClapperImporter *importer, GstQuery *query)
{
/* Importer base class handles GstVideoOverlayCompositionMeta */
gst_query_add_allocation_meta (query, GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL);
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
}
static GdkTexture *
_default_generate_texture (GstClapperImporter *self,
GstBuffer *buffer, GstVideoInfo *v_info)
{
GST_FIXME_OBJECT (self, "GdkTexture generation not implemented");
return NULL;
}
static void
gst_clapper_importer_init (GstClapperImporter *self)
{
gst_video_info_init (&self->pending_v_info);
gst_video_info_init (&self->v_info);
self->pending_overlays = g_ptr_array_new_with_free_func (
(GDestroyNotify) gst_clapper_gdk_overlay_unref);
self->overlays = g_ptr_array_new_with_free_func (
(GDestroyNotify) gst_clapper_gdk_overlay_unref);
gdk_rgba_parse (&self->bg, "black");
}
static void
gst_clapper_importer_finalize (GObject *object)
{
GstClapperImporter *self = GST_CLAPPER_IMPORTER_CAST (object);
GST_TRACE ("Finalize");
gst_clear_caps (&self->pending_caps);
gst_clear_buffer (&self->pending_buffer);
gst_clear_buffer (&self->buffer);
g_ptr_array_unref (self->pending_overlays);
g_ptr_array_unref (self->overlays);
g_clear_object (&self->texture);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
gst_clapper_importer_class_init (GstClapperImporterClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstClapperImporterClass *importer_class = (GstClapperImporterClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperimporter", 0,
"Clapper Importer");
gobject_class->finalize = gst_clapper_importer_finalize;
importer_class->create_pool = _default_create_pool;
importer_class->add_allocation_metas = _default_add_allocation_metas;
importer_class->generate_texture = _default_generate_texture;
}
static GstClapperGdkOverlay *
_get_cached_overlay (GPtrArray *overlays, GstVideoOverlayRectangle *rectangle)
{
guint i;
for (i = 0; i < overlays->len; i++) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (overlays, i);
if (overlay->rectangle == rectangle)
return overlay;
}
return NULL;
}
static gint
_sort_overlays_cb (gconstpointer a, gconstpointer b)
{
GstClapperGdkOverlay *overlay_a, *overlay_b;
overlay_a = *((GstClapperGdkOverlay **) a);
overlay_b = *((GstClapperGdkOverlay **) b);
return (overlay_a->index - overlay_b->index);
}
/*
* Prepares overlays to show with the next rendered buffer.
*
* In order for overlays caching to work correctly, this should be called for
* every received buffer (even if its going to be disgarded), also must be
* called together with pending buffer replacement within a single importer
* locking, to make sure prepared overlays always match the pending buffer.
*/
static void
gst_clapper_importer_prepare_overlays_locked (GstClapperImporter *self)
{
GstVideoOverlayCompositionMeta *comp_meta;
guint num_overlays, i;
if (G_UNLIKELY (!self->pending_buffer)
|| !(comp_meta = gst_buffer_get_video_overlay_composition_meta (self->pending_buffer))) {
guint n_pending = self->pending_overlays->len;
/* Remove all cached overlays if new buffer does not have any */
if (n_pending > 0) {
GST_TRACE ("No overlays in buffer, removing all cached ones");
g_ptr_array_remove_range (self->pending_overlays, 0, n_pending);
}
return;
}
GST_LOG_OBJECT (self, "Preparing overlays...");
/* Mark all old overlays as unused by giving them negative index */
for (i = 0; i < self->pending_overlays->len; i++) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (self->pending_overlays, i);
overlay->index = -1;
}
num_overlays = gst_video_overlay_composition_n_rectangles (comp_meta->overlay);
for (i = 0; i < num_overlays; i++) {
GdkTexture *texture;
GstBuffer *comp_buffer;
GstVideoFrame comp_frame;
GstVideoMeta *v_meta;
GstVideoInfo v_info;
GstVideoOverlayRectangle *rectangle;
GstClapperGdkOverlay *overlay;
GstVideoOverlayFormatFlags flags, alpha_flags = 0;
gint comp_x, comp_y;
guint comp_width, comp_height;
rectangle = gst_video_overlay_composition_get_rectangle (comp_meta->overlay, i);
if ((overlay = _get_cached_overlay (self->pending_overlays, rectangle))) {
overlay->index = i;
GST_TRACE ("Reusing cached overlay: %" GST_PTR_FORMAT, overlay);
continue;
}
if (G_UNLIKELY (!gst_video_overlay_rectangle_get_render_rectangle (rectangle,
&comp_x, &comp_y, &comp_width, &comp_height))) {
GST_WARNING ("Invalid overlay rectangle dimensions: %" GST_PTR_FORMAT, rectangle);
continue;
}
flags = gst_video_overlay_rectangle_get_flags (rectangle);
if (flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA)
alpha_flags |= GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA;
comp_buffer = gst_video_overlay_rectangle_get_pixels_unscaled_argb (rectangle, alpha_flags);
/* Update overlay video info from video meta */
if ((v_meta = gst_buffer_get_video_meta (comp_buffer))) {
gst_video_info_set_format (&v_info, v_meta->format, v_meta->width, v_meta->height);
v_info.stride[0] = v_meta->stride[0];
if (alpha_flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA)
v_info.flags |= GST_VIDEO_FLAG_PREMULTIPLIED_ALPHA;
}
if (G_UNLIKELY (!gst_video_frame_map (&comp_frame, &v_info, comp_buffer, GST_MAP_READ)))
return;
if ((texture = gst_video_frame_into_gdk_texture (&comp_frame))) {
overlay = gst_clapper_gdk_overlay_new (texture, rectangle, comp_x, comp_y,
comp_width, comp_height, i);
g_object_unref (texture);
GST_TRACE_OBJECT (self, "Created overlay: %"
GST_PTR_FORMAT ", x: %i, y: %i, width: %u, height: %u",
overlay, overlay->x, overlay->y, overlay->width, overlay->height);
g_ptr_array_insert (self->pending_overlays, i, overlay);
}
gst_video_frame_unmap (&comp_frame);
}
/* Remove all overlays that are not going to be used */
for (i = self->pending_overlays->len; i > 0; i--) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (self->pending_overlays, i - 1);
if (overlay->index < 0) {
GST_TRACE ("Removing unused cached overlay: %" GST_PTR_FORMAT, overlay);
g_ptr_array_remove (self->pending_overlays, overlay);
}
}
/* Sort remaining overlays */
if (self->pending_overlays->len > 1) {
GST_LOG_OBJECT (self, "Sorting overlays");
g_ptr_array_sort (self->pending_overlays, (GCompareFunc) _sort_overlays_cb);
}
if (G_UNLIKELY (num_overlays != self->pending_overlays->len)) {
GST_WARNING_OBJECT (self, "Some overlays could not be prepared, %u != %u",
num_overlays, self->pending_overlays->len);
}
GST_LOG_OBJECT (self, "Prepared overlays: %u", self->pending_overlays->len);
}
void
gst_clapper_importer_set_caps (GstClapperImporter *self, GstCaps *caps)
{
GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self);
GST_OBJECT_LOCK (self);
gst_caps_replace (&self->pending_caps, caps);
GST_OBJECT_UNLOCK (self);
if (importer_class->set_caps)
importer_class->set_caps (self, caps);
}
void
gst_clapper_importer_set_buffer (GstClapperImporter *self, GstBuffer *buffer)
{
GST_OBJECT_LOCK (self);
/* Pending v_info, buffer and overlays must be
* set within a single importer locking */
if (self->pending_caps) {
self->has_pending_v_info = gst_video_info_from_caps (&self->pending_v_info, self->pending_caps);
gst_clear_caps (&self->pending_caps);
}
gst_buffer_replace (&self->pending_buffer, buffer);
gst_clapper_importer_prepare_overlays_locked (self);
GST_OBJECT_UNLOCK (self);
}
GstBufferPool *
gst_clapper_importer_create_pool (GstClapperImporter *self, GstStructure **config)
{
GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self);
return importer_class->create_pool (self, config);
}
void
gst_clapper_importer_add_allocation_metas (GstClapperImporter *self, GstQuery *query)
{
GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self);
importer_class->add_allocation_metas (self, query);
}
void
gst_clapper_importer_snapshot (GstClapperImporter *self, GdkSnapshot *snapshot,
gdouble width, gdouble height)
{
guint i;
gboolean buffer_changed;
/* Collect all data that we need to snapshot pending buffer,
* lock ourselves to make sure everything matches */
GST_OBJECT_LOCK (self);
if (self->has_pending_v_info) {
self->v_info = self->pending_v_info;
self->has_pending_v_info = FALSE;
}
buffer_changed = gst_buffer_replace (&self->buffer, self->pending_buffer);
/* Ref overlays associated with current buffer */
for (i = 0; i < self->pending_overlays->len; i++) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (self->pending_overlays, i);
g_ptr_array_insert (self->overlays, i, gst_clapper_gdk_overlay_ref (overlay));
}
GST_OBJECT_UNLOCK (self);
/* Draw black BG when no buffer or imported format has alpha */
if (!self->buffer || GST_VIDEO_INFO_HAS_ALPHA (&self->v_info))
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height));
if (self->buffer) {
if (buffer_changed || !self->texture) {
GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self);
GST_TRACE_OBJECT (self, "Importing %" GST_PTR_FORMAT, self->buffer);
g_clear_object (&self->texture);
self->texture = importer_class->generate_texture (self, self->buffer, &self->v_info);
} else {
GST_TRACE_OBJECT (self, "Reusing texture from %" GST_PTR_FORMAT, self->buffer);
}
if (G_LIKELY (self->texture)) {
gtk_snapshot_append_texture (snapshot, self->texture, &GRAPHENE_RECT_INIT (0, 0, width, height));
if (self->overlays->len > 0) {
gfloat scale_x, scale_y;
/* FIXME: GStreamer scales subtitles without considering pixel aspect ratio.
* See: https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/20 */
scale_x = (gfloat) width / GST_VIDEO_INFO_WIDTH (&self->v_info);
scale_y = (gfloat) height / GST_VIDEO_INFO_HEIGHT (&self->v_info);
for (i = 0; i < self->overlays->len; i++) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (self->overlays, i);
gtk_snapshot_append_texture (snapshot, overlay->texture,
&GRAPHENE_RECT_INIT (overlay->x * scale_x, overlay->y * scale_y,
overlay->width * scale_x, overlay->height * scale_y));
}
}
} else {
GST_ERROR_OBJECT (self, "Failed import of %" GST_PTR_FORMAT, self->buffer);
/* Draw black instead of texture on failure if not drawn already */
if (!GST_VIDEO_INFO_HAS_ALPHA (&self->v_info))
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height));
}
}
/* Unref all used overlays */
if (self->overlays->len > 0)
g_ptr_array_remove_range (self->overlays, 0, self->overlays->len);
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gtk/gtk.h>
#include <gst/gst.h>
#include <gst/video/video.h>
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_IMPORTER (gst_clapper_importer_get_type())
#define GST_IS_CLAPPER_IMPORTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_IMPORTER))
#define GST_IS_CLAPPER_IMPORTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_IMPORTER))
#define GST_CLAPPER_IMPORTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_IMPORTER, GstClapperImporterClass))
#define GST_CLAPPER_IMPORTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_IMPORTER, GstClapperImporter))
#define GST_CLAPPER_IMPORTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_IMPORTER, GstClapperImporterClass))
#define GST_CLAPPER_IMPORTER_CAST(obj) ((GstClapperImporter *)(obj))
#define GST_CLAPPER_IMPORTER_DEFINE(camel,lower,type) \
G_DEFINE_TYPE (camel, lower, type) \
G_MODULE_EXPORT GstClapperImporter *make_importer (GPtrArray *context_handlers); \
G_MODULE_EXPORT GstCaps *make_caps (gboolean is_template, \
GstRank *rank, GPtrArray *context_handlers);
typedef struct _GstClapperImporter GstClapperImporter;
typedef struct _GstClapperImporterClass GstClapperImporterClass;
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperImporter, gst_object_unref)
#endif
struct _GstClapperImporter
{
GstObject parent;
GstCaps *pending_caps;
GstBuffer *pending_buffer, *buffer;
GPtrArray *pending_overlays, *overlays;
GstVideoInfo pending_v_info, v_info;
gboolean has_pending_v_info;
GdkTexture *texture;
GdkRGBA bg;
};
struct _GstClapperImporterClass
{
GstObjectClass parent_class;
void (* set_caps) (GstClapperImporter *importer,
GstCaps *caps);
GstBufferPool * (* create_pool) (GstClapperImporter *importer,
GstStructure **config);
void (* add_allocation_metas) (GstClapperImporter *importer,
GstQuery *query);
GdkTexture * (* generate_texture) (GstClapperImporter *importer,
GstBuffer *buffer,
GstVideoInfo *v_info);
};
GType gst_clapper_importer_get_type (void);
GstBufferPool * gst_clapper_importer_create_pool (GstClapperImporter *importer, GstStructure **config);
void gst_clapper_importer_add_allocation_metas (GstClapperImporter *importer, GstQuery *query);
void gst_clapper_importer_set_caps (GstClapperImporter *importer, GstCaps *caps);
void gst_clapper_importer_set_buffer (GstClapperImporter *importer, GstBuffer *buffer);
void gst_clapper_importer_snapshot (GstClapperImporter *importer, GdkSnapshot *snapshot, gdouble width, gdouble height);
G_END_DECLS

View File

@@ -0,0 +1,385 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gmodule.h>
#include "gstclapperimporterloader.h"
#include "gstclapperimporter.h"
#include "gstclappercontexthandler.h"
#define GST_CAT_DEFAULT gst_clapper_importer_loader_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define parent_class gst_clapper_importer_loader_parent_class
G_DEFINE_TYPE (GstClapperImporterLoader, gst_clapper_importer_loader, GST_TYPE_OBJECT);
typedef GstClapperImporter* (* MakeImporter) (GPtrArray *context_handlers);
typedef GstCaps* (* MakeCaps) (gboolean is_template, GstRank *rank, GPtrArray *context_handlers);
typedef struct
{
GModule *module;
GstCaps *caps;
GstRank rank;
} GstClapperImporterData;
static void
gst_clapper_importer_data_free (GstClapperImporterData *data)
{
GST_TRACE ("Freeing importer data: %" GST_PTR_FORMAT, data);
gst_clear_caps (&data->caps);
g_free (data);
}
static GstClapperImporterData *
_obtain_importer_data (GModule *module, gboolean is_template, GPtrArray *context_handlers)
{
MakeCaps make_caps;
GstClapperImporterData *data;
GST_DEBUG ("Found importer: %s", g_module_name (module));
if (!g_module_symbol (module, "make_caps", (gpointer *) &make_caps)
|| make_caps == NULL) {
GST_WARNING ("Make caps function missing in importer");
return NULL;
}
data = g_new0 (GstClapperImporterData, 1);
data->module = module;
data->caps = make_caps (is_template, &data->rank, context_handlers);
GST_TRACE ("Created importer data: %" GST_PTR_FORMAT, data);
if (G_UNLIKELY (!data->caps)) {
if (!is_template) {
GST_ERROR ("Invalid importer without caps: %s",
g_module_name (data->module));
} else {
/* When importer cannot be actually used, due to e.g. unsupported HW */
GST_DEBUG ("No actual caps returned from importer");
}
gst_clapper_importer_data_free (data);
return NULL;
}
GST_DEBUG ("Importer caps: %" GST_PTR_FORMAT, data->caps);
return data;
}
static GstClapperImporter *
_obtain_importer_internal (GModule *module, GPtrArray *context_handlers)
{
MakeImporter make_importer;
GstClapperImporter *importer;
if (!g_module_symbol (module, "make_importer", (gpointer *) &make_importer)
|| make_importer == NULL) {
GST_WARNING ("Make function missing in importer");
return NULL;
}
importer = make_importer (context_handlers);
GST_TRACE ("Created importer: %" GST_PTR_FORMAT, importer);
return importer;
}
static gpointer
_obtain_available_modules_once (G_GNUC_UNUSED gpointer data)
{
GPtrArray *modules;
GFile *dir;
GFileEnumerator *dir_enum;
GError *error = NULL;
GST_INFO ("Preparing modules");
modules = g_ptr_array_new ();
dir = g_file_new_for_path (CLAPPER_SINK_IMPORTER_PATH);
if ((dir_enum = g_file_enumerate_children (dir,
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error))) {
while (TRUE) {
GFileInfo *info = NULL;
GModule *module;
gchar *module_path;
const gchar *module_name;
if (!g_file_enumerator_iterate (dir_enum, &info,
NULL, NULL, &error) || !info)
break;
module_name = g_file_info_get_name (info);
if (!g_str_has_suffix (module_name, G_MODULE_SUFFIX))
continue;
module_path = g_module_build_path (CLAPPER_SINK_IMPORTER_PATH, module_name);
module = g_module_open (module_path, G_MODULE_BIND_LAZY);
g_free (module_path);
if (!module) {
GST_WARNING ("Could not read module: %s, reason: %s",
module_name, g_module_error ());
continue;
}
GST_INFO ("Found module: %s", module_name);
g_ptr_array_add (modules, module);
}
g_object_unref (dir_enum);
}
g_object_unref (dir);
if (error) {
GST_ERROR ("Could not load module, reason: %s",
(error->message) ? error->message : "unknown");
g_error_free (error);
}
return modules;
}
static const GPtrArray *
gst_clapper_importer_loader_get_available_modules (void)
{
static GOnce once = G_ONCE_INIT;
g_once (&once, _obtain_available_modules_once, NULL);
return (const GPtrArray *) once.retval;
}
static gint
_sort_importers_cb (gconstpointer a, gconstpointer b)
{
GstClapperImporterData *data_a, *data_b;
data_a = *((GstClapperImporterData **) a);
data_b = *((GstClapperImporterData **) b);
return (data_b->rank - data_a->rank);
}
static GPtrArray *
_obtain_importers (gboolean is_template, GPtrArray *context_handlers)
{
const GPtrArray *modules;
GPtrArray *importers;
guint i;
GST_DEBUG ("Checking %s importers",
(is_template) ? "available" : "usable");
modules = gst_clapper_importer_loader_get_available_modules ();
importers = g_ptr_array_new_with_free_func (
(GDestroyNotify) gst_clapper_importer_data_free);
for (i = 0; i < modules->len; i++) {
GModule *module = g_ptr_array_index (modules, i);
GstClapperImporterData *data;
if ((data = _obtain_importer_data (module, is_template, context_handlers)))
g_ptr_array_add (importers, data);
}
g_ptr_array_sort (importers, (GCompareFunc) _sort_importers_cb);
GST_DEBUG ("Found %i %s importers", importers->len,
(is_template) ? "available" : "usable");
return importers;
}
GstClapperImporterLoader *
gst_clapper_importer_loader_new (void)
{
return g_object_new (GST_TYPE_CLAPPER_IMPORTER_LOADER, NULL);
}
static GstCaps *
_make_caps_for_importers (const GPtrArray *importers)
{
GstCaps *caps = gst_caps_new_empty ();
guint i;
for (i = 0; i < importers->len; i++) {
GstClapperImporterData *data = g_ptr_array_index (importers, i);
gst_caps_append (caps, gst_caps_ref (data->caps));
}
return caps;
}
GstPadTemplate *
gst_clapper_importer_loader_make_sink_pad_template (void)
{
GPtrArray *importers;
GstCaps *caps;
GstPadTemplate *templ;
/* This is only called once from sink class init function */
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperimporterloader", 0,
"Clapper Importer Loader");
GST_DEBUG ("Making sink pad template");
importers = _obtain_importers (TRUE, NULL);
caps = _make_caps_for_importers (importers);
g_ptr_array_unref (importers);
if (G_UNLIKELY (gst_caps_is_empty (caps)))
gst_caps_append (caps, gst_caps_new_any ());
templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, caps);
gst_caps_unref (caps);
GST_TRACE ("Created sink pad template");
return templ;
}
GstCaps *
gst_clapper_importer_loader_make_actual_caps (GstClapperImporterLoader *self)
{
return _make_caps_for_importers (self->importers);
}
gboolean
gst_clapper_importer_loader_handle_context_query (GstClapperImporterLoader *self,
GstBaseSink *bsink, GstQuery *query)
{
guint i;
for (i = 0; i < self->context_handlers->len; i++) {
GstClapperContextHandler *handler = g_ptr_array_index (self->context_handlers, i);
if (gst_clapper_context_handler_handle_context_query (handler, bsink, query))
return TRUE;
}
return FALSE;
}
static const GstClapperImporterData *
_get_importer_data_for_caps (const GPtrArray *importers, const GstCaps *caps)
{
guint i;
for (i = 0; i < importers->len; i++) {
GstClapperImporterData *data = g_ptr_array_index (importers, i);
if (!gst_caps_is_always_compatible (caps, data->caps))
continue;
return data;
}
return NULL;
}
gboolean
gst_clapper_importer_loader_find_importer_for_caps (GstClapperImporterLoader *self,
GstCaps *caps, GstClapperImporter **importer)
{
const GstClapperImporterData *data = NULL;
GstClapperImporter *found_importer = NULL;
GST_OBJECT_LOCK (self);
GST_DEBUG_OBJECT (self, "Requested importer for caps: %" GST_PTR_FORMAT, caps);
data = _get_importer_data_for_caps (self->importers, caps);
GST_LOG_OBJECT (self, "Old importer path: %s, new path: %s",
(self->last_module) ? g_module_name (self->last_module) : NULL,
(data) ? g_module_name (data->module) : NULL);
if (G_UNLIKELY (!data)) {
gst_clear_object (importer);
goto finish;
}
if (*importer && (self->last_module == data->module)) {
GST_DEBUG_OBJECT (self, "No importer change");
gst_clapper_importer_set_caps (*importer, caps);
goto finish;
}
found_importer = _obtain_importer_internal (data->module, self->context_handlers);
gst_clear_object (importer);
if (!found_importer)
goto finish;
gst_clapper_importer_set_caps (found_importer, caps);
*importer = found_importer;
finish:
self->last_module = (*importer && data)
? data->module
: NULL;
GST_OBJECT_UNLOCK (self);
return (*importer != NULL);
}
static void
gst_clapper_importer_loader_init (GstClapperImporterLoader *self)
{
self->context_handlers = g_ptr_array_new_with_free_func (
(GDestroyNotify) gst_object_unref);
self->importers = _obtain_importers (FALSE, self->context_handlers);
}
static void
gst_clapper_importer_loader_finalize (GObject *object)
{
GstClapperImporterLoader *self = GST_CLAPPER_IMPORTER_LOADER_CAST (object);
GST_TRACE ("Finalize");
if (self->importers)
g_ptr_array_unref (self->importers);
g_ptr_array_unref (self->context_handlers);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
gst_clapper_importer_loader_class_init (GstClapperImporterLoaderClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
gobject_class->finalize = gst_clapper_importer_loader_finalize;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <gst/base/gstbasesink.h>
#include "gstclapperimporter.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_IMPORTER_LOADER (gst_clapper_importer_loader_get_type())
G_DECLARE_FINAL_TYPE (GstClapperImporterLoader, gst_clapper_importer_loader, GST, CLAPPER_IMPORTER_LOADER, GstObject)
#define GST_CLAPPER_IMPORTER_LOADER_CAST(obj) ((GstClapperImporterLoader *)(obj))
struct _GstClapperImporterLoader
{
GstObject parent;
GModule *last_module;
GPtrArray *importers;
GPtrArray *context_handlers;
};
GstClapperImporterLoader * gst_clapper_importer_loader_new (void);
GstPadTemplate * gst_clapper_importer_loader_make_sink_pad_template (void);
GstCaps * gst_clapper_importer_loader_make_actual_caps (GstClapperImporterLoader *loader);
gboolean gst_clapper_importer_loader_handle_context_query (GstClapperImporterLoader *loader, GstBaseSink *bsink, GstQuery *query);
gboolean gst_clapper_importer_loader_find_importer_for_caps (GstClapperImporterLoader *loader, GstCaps *caps, GstClapperImporter **importer);
G_END_DECLS

View File

@@ -0,0 +1,530 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstclapperpaintable.h"
#include "gstgtkutils.h"
#define DEFAULT_PAR_N 1
#define DEFAULT_PAR_D 1
#define GST_CAT_DEFAULT gst_clapper_paintable_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
static void gst_clapper_paintable_iface_init (GdkPaintableInterface *iface);
static void gst_clapper_paintable_dispose (GObject *object);
static void gst_clapper_paintable_finalize (GObject *object);
#define parent_class gst_clapper_paintable_parent_class
G_DEFINE_TYPE_WITH_CODE (GstClapperPaintable, gst_clapper_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gst_clapper_paintable_iface_init));
static void
gst_clapper_paintable_class_init (GstClapperPaintableClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperpaintable", 0,
"Clapper Paintable");
gobject_class->dispose = gst_clapper_paintable_dispose;
gobject_class->finalize = gst_clapper_paintable_finalize;
}
static void
gst_clapper_paintable_init (GstClapperPaintable *self)
{
self->display_width = 1;
self->display_height = 1;
self->display_aspect_ratio = 1.0;
self->rotation = GST_VIDEO_ORIENTATION_IDENTITY;
self->par_n = DEFAULT_PAR_N;
self->par_d = DEFAULT_PAR_D;
g_mutex_init (&self->lock);
g_mutex_init (&self->importer_lock);
gst_video_info_init (&self->v_info);
g_weak_ref_init (&self->widget, NULL);
gdk_rgba_parse (&self->bg, "black");
}
static void
gst_clapper_paintable_dispose (GObject *object)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE (object);
GST_CLAPPER_PAINTABLE_LOCK (self);
if (self->draw_id > 0) {
g_source_remove (self->draw_id);
self->draw_id = 0;
}
GST_CLAPPER_PAINTABLE_UNLOCK (self);
GST_CLAPPER_PAINTABLE_IMPORTER_LOCK (self);
gst_clear_object (&self->importer);
GST_CLAPPER_PAINTABLE_IMPORTER_UNLOCK (self);
GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
}
static void
gst_clapper_paintable_finalize (GObject *object)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE (object);
GST_TRACE ("Finalize");
g_weak_ref_clear (&self->widget);
g_mutex_clear (&self->lock);
g_mutex_clear (&self->importer_lock);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static gboolean
calculate_display_par (GstClapperPaintable *self, const GstVideoInfo *info)
{
gint width, height, par_n, par_d, req_par_n, req_par_d;
gboolean success;
gst_gtk_get_width_height_for_rotation (GST_VIDEO_INFO_WIDTH (info),
GST_VIDEO_INFO_HEIGHT (info), &width, &height, self->rotation);
/* Cannot apply aspect ratio if there is no video */
if (width == 0 || height == 0)
return FALSE;
par_n = GST_VIDEO_INFO_PAR_N (info);
par_d = GST_VIDEO_INFO_PAR_D (info);
req_par_n = self->par_n;
req_par_d = self->par_d;
if (par_n == 0)
par_n = 1;
/* Use defaults if user set zero */
if (req_par_n == 0 || req_par_d == 0) {
req_par_n = DEFAULT_PAR_N;
req_par_d = DEFAULT_PAR_D;
}
GST_LOG_OBJECT (self, "PAR: %u/%u, DAR: %u/%u", par_n, par_d, req_par_n, req_par_d);
if (!(success = gst_video_calculate_display_ratio (&self->display_ratio_num,
&self->display_ratio_den, width, height, par_n, par_d,
req_par_n, req_par_d))) {
GST_ERROR_OBJECT (self, "Could not calculate display ratio values");
}
return success;
}
static void
invalidate_paintable_size_internal (GstClapperPaintable *self)
{
gint video_width, video_height;
guint display_ratio_num, display_ratio_den;
GST_CLAPPER_PAINTABLE_LOCK (self);
gst_gtk_get_width_height_for_rotation (GST_VIDEO_INFO_WIDTH (&self->v_info),
GST_VIDEO_INFO_HEIGHT (&self->v_info), &video_height, &video_width,
self->rotation);
display_ratio_num = self->display_ratio_num;
display_ratio_den = self->display_ratio_den;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
if (video_height % display_ratio_den == 0) {
GST_LOG ("Keeping video height");
self->display_width = (guint)
gst_util_uint64_scale_int (video_height, display_ratio_num, display_ratio_den);
self->display_height = video_height;
} else if (video_width % display_ratio_num == 0) {
GST_LOG ("Keeping video width");
self->display_width = video_width;
self->display_height = (guint)
gst_util_uint64_scale_int (video_width, display_ratio_den, display_ratio_num);
} else {
GST_LOG ("Approximating while keeping video height");
self->display_width = (guint)
gst_util_uint64_scale_int (video_height, display_ratio_num, display_ratio_den);
self->display_height = video_height;
}
self->display_aspect_ratio = ((gdouble) self->display_width
/ (gdouble) self->display_height);
GST_DEBUG_OBJECT (self, "Invalidate paintable size, display: %dx%d",
self->display_width, self->display_height);
gdk_paintable_invalidate_size ((GdkPaintable *) self);
}
static gboolean
invalidate_paintable_size_on_main_cb (GstClapperPaintable *self)
{
GST_CLAPPER_PAINTABLE_LOCK (self);
self->draw_id = 0;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
invalidate_paintable_size_internal (self);
return G_SOURCE_REMOVE;
}
static gboolean
update_paintable_on_main_cb (GstClapperPaintable *self)
{
gboolean size_changed;
GST_CLAPPER_PAINTABLE_LOCK (self);
/* Check if we will need to invalidate size */
if ((size_changed = self->pending_resize))
self->pending_resize = FALSE;
self->draw_id = 0;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
if (size_changed)
invalidate_paintable_size_internal (self);
GST_LOG_OBJECT (self, "Invalidate paintable contents");
gdk_paintable_invalidate_contents ((GdkPaintable *) self);
return G_SOURCE_REMOVE;
}
GstClapperPaintable *
gst_clapper_paintable_new (void)
{
return g_object_new (GST_TYPE_CLAPPER_PAINTABLE, NULL);
}
void
gst_clapper_paintable_set_widget (GstClapperPaintable *self, GtkWidget *widget)
{
g_weak_ref_set (&self->widget, widget);
}
void
gst_clapper_paintable_set_importer (GstClapperPaintable *self, GstClapperImporter *importer)
{
GST_CLAPPER_PAINTABLE_IMPORTER_LOCK (self);
gst_object_replace ((GstObject **) &self->importer, GST_OBJECT_CAST (importer));
GST_CLAPPER_PAINTABLE_IMPORTER_UNLOCK (self);
}
void
gst_clapper_paintable_queue_draw (GstClapperPaintable *self)
{
GST_CLAPPER_PAINTABLE_LOCK (self);
if (self->draw_id > 0) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
GST_TRACE ("Already have pending draw");
return;
}
self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
(GSourceFunc) update_paintable_on_main_cb, self, NULL);
GST_CLAPPER_PAINTABLE_UNLOCK (self);
}
gboolean
gst_clapper_paintable_set_video_info (GstClapperPaintable *self, const GstVideoInfo *v_info)
{
GST_CLAPPER_PAINTABLE_LOCK (self);
if (gst_video_info_is_equal (&self->v_info, v_info)) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return TRUE;
}
/* Reject info if values would cause integer overflow */
if (G_UNLIKELY (!calculate_display_par (self, v_info))) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return FALSE;
}
self->pending_resize = TRUE;
self->v_info = *v_info;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return TRUE;
}
void
gst_clapper_paintable_set_pixel_aspect_ratio (GstClapperPaintable *self,
gint par_n, gint par_d)
{
gboolean success;
GST_CLAPPER_PAINTABLE_LOCK (self);
/* No change */
if (self->par_n == par_n && self->par_d == par_d) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return;
}
self->par_n = par_n;
self->par_d = par_d;
/* Check if we can accept new values. This will update
* display `ratio_num` and `ratio_den` only when successful */
success = calculate_display_par (self, &self->v_info);
/* If paintable update is queued, wait for it, otherwise invalidate
* size only for change to be applied even when paused */
if (!success || self->draw_id > 0) {
self->pending_resize = success;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return;
}
self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
(GSourceFunc) invalidate_paintable_size_on_main_cb, self, NULL);
GST_CLAPPER_PAINTABLE_UNLOCK (self);
}
void
gst_clapper_paintable_set_rotation (GstClapperPaintable *self,
GstVideoOrientationMethod rotation)
{
GST_CLAPPER_PAINTABLE_LOCK (self);
self->rotation = rotation;
if (G_UNLIKELY (!calculate_display_par (self, &self->v_info))) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return;
}
self->pending_resize = TRUE;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
}
GstVideoOrientationMethod
gst_clapper_paintable_get_rotation (GstClapperPaintable *self)
{
GstVideoOrientationMethod rotation;
GST_CLAPPER_PAINTABLE_LOCK (self);
rotation = self->rotation;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return rotation;
}
/*
* GdkPaintableInterface
*/
static void
gst_clapper_paintable_snapshot_internal (GstClapperPaintable *self,
GdkSnapshot *snapshot, gdouble width, gdouble height,
gint widget_width, gint widget_height)
{
gfloat scale_x, scale_y;
gdouble snapshot_width, snapshot_height;
GskTransform *transform = NULL;
GST_LOG_OBJECT (self, "Snapshot");
scale_x = (gfloat) width / self->display_width;
scale_y = (gfloat) height / self->display_height;
/* Apply black borders when keeping aspect ratio */
if (scale_x == scale_y || abs (scale_x - scale_y) <= FLT_EPSILON) {
if (widget_height - height > 0) {
/* XXX: Top uses integer to work with GTK rounding (not going offscreen) */
gint top_bar_height = (widget_height - height) / 2;
gdouble bottom_bar_height = (widget_height - top_bar_height - height);
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, -top_bar_height));
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, height, width, bottom_bar_height));
} else if (widget_width - width > 0) {
gint left_bar_width = (widget_width - width) / 2;
gdouble right_bar_width = (widget_width - left_bar_width - width);
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, -left_bar_width, height));
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (width, 0, right_bar_width, height));
}
}
GST_CLAPPER_PAINTABLE_IMPORTER_LOCK (self);
if (self->importer) {
switch (self->rotation) {
case GST_VIDEO_ORIENTATION_IDENTITY:
default:
snapshot_width = width;
snapshot_height = height;
break;
case GST_VIDEO_ORIENTATION_90R:
transform = gsk_transform_rotate (transform, 90);
transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, -width));
snapshot_width = height;
snapshot_height = width;
break;
case GST_VIDEO_ORIENTATION_180:
transform = gsk_transform_rotate (transform, 180);
transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-width, -height));
snapshot_width = width;
snapshot_height = height;
break;
case GST_VIDEO_ORIENTATION_90L:
transform = gsk_transform_rotate (transform, 270);
transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-height, 0));
snapshot_width = height;
snapshot_height = width;
break;
case GST_VIDEO_ORIENTATION_HORIZ:
transform = gsk_transform_rotate_3d (transform, 180, graphene_vec3_y_axis ());
transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-width, 0));
snapshot_width = width;
snapshot_height = height;
break;
case GST_VIDEO_ORIENTATION_VERT:
transform = gsk_transform_rotate_3d (transform, 180, graphene_vec3_x_axis ());
transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, -height));
snapshot_width = width;
snapshot_height = height;
break;
case GST_VIDEO_ORIENTATION_UL_LR:
transform = gsk_transform_rotate (transform, 90);
transform = gsk_transform_rotate_3d (transform, 180, graphene_vec3_x_axis ());
snapshot_width = height;
snapshot_height = width;
break;
case GST_VIDEO_ORIENTATION_UR_LL:
transform = gsk_transform_rotate (transform, 90);
transform = gsk_transform_rotate_3d (transform, 180, graphene_vec3_y_axis ());
transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-height, -width));
snapshot_width = height;
snapshot_height = width;
break;
}
if (transform) {
gtk_snapshot_transform (snapshot, transform);
gsk_transform_unref (transform);
}
gst_clapper_importer_snapshot (self->importer, snapshot, snapshot_width, snapshot_height);
} else {
GST_LOG_OBJECT (self, "No texture importer, drawing black");
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height));
}
GST_CLAPPER_PAINTABLE_IMPORTER_UNLOCK (self);
}
static void
gst_clapper_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot, gdouble width, gdouble height)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
GtkWidget *widget;
gint widget_width = 0, widget_height = 0;
if ((widget = g_weak_ref_get (&self->widget))) {
widget_width = gtk_widget_get_width (widget);
widget_height = gtk_widget_get_height (widget);
g_object_unref (widget);
}
gst_clapper_paintable_snapshot_internal (self, snapshot,
width, height, widget_width, widget_height);
}
static GdkPaintable *
gst_clapper_paintable_get_current_image (GdkPaintable *paintable)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
GtkSnapshot *snapshot = gtk_snapshot_new ();
/* Snapshot without widget size in order to get
* paintable without black borders */
gst_clapper_paintable_snapshot_internal (self, snapshot,
self->display_width, self->display_height, 0, 0);
return gtk_snapshot_free_to_paintable (snapshot, NULL);
}
static gint
gst_clapper_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
return self->display_width;
}
static gint
gst_clapper_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
return self->display_height;
}
static gdouble
gst_clapper_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
return self->display_aspect_ratio;
}
static void
gst_clapper_paintable_iface_init (GdkPaintableInterface *iface)
{
iface->snapshot = gst_clapper_paintable_snapshot;
iface->get_current_image = gst_clapper_paintable_get_current_image;
iface->get_intrinsic_width = gst_clapper_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gst_clapper_paintable_get_intrinsic_height;
iface->get_intrinsic_aspect_ratio = gst_clapper_paintable_get_intrinsic_aspect_ratio;
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gtk/gtk.h>
#include <gst/gst.h>
#include <gst/video/video.h>
#include "gstclapperimporter.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_PAINTABLE (gst_clapper_paintable_get_type())
G_DECLARE_FINAL_TYPE (GstClapperPaintable, gst_clapper_paintable, GST, CLAPPER_PAINTABLE, GObject)
#define GST_CLAPPER_PAINTABLE_CAST(obj) ((GstClapperPaintable *)(obj))
#define GST_CLAPPER_PAINTABLE_GET_LOCK(obj) (&GST_CLAPPER_PAINTABLE_CAST(obj)->lock)
#define GST_CLAPPER_PAINTABLE_LOCK(obj) g_mutex_lock (GST_CLAPPER_PAINTABLE_GET_LOCK(obj))
#define GST_CLAPPER_PAINTABLE_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_PAINTABLE_GET_LOCK(obj))
#define GST_CLAPPER_PAINTABLE_IMPORTER_GET_LOCK(obj) (&GST_CLAPPER_PAINTABLE_CAST(obj)->importer_lock)
#define GST_CLAPPER_PAINTABLE_IMPORTER_LOCK(obj) g_mutex_lock (GST_CLAPPER_PAINTABLE_IMPORTER_GET_LOCK(obj))
#define GST_CLAPPER_PAINTABLE_IMPORTER_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_PAINTABLE_IMPORTER_GET_LOCK(obj))
struct _GstClapperPaintable
{
GObject parent;
GMutex lock;
GMutex importer_lock;
GstVideoInfo v_info;
GdkRGBA bg;
GWeakRef widget;
GstClapperImporter *importer;
/* Sink properties */
gint par_n, par_d;
GstVideoOrientationMethod rotation;
/* Resize */
gboolean pending_resize;
guint display_ratio_num;
guint display_ratio_den;
/* GdkPaintableInterface */
gint display_width;
gint display_height;
gdouble display_aspect_ratio;
/* Pending draw signal id */
guint draw_id;
};
GstClapperPaintable * gst_clapper_paintable_new (void);
void gst_clapper_paintable_queue_draw (GstClapperPaintable *paintable);
void gst_clapper_paintable_set_widget (GstClapperPaintable *paintable, GtkWidget *widget);
void gst_clapper_paintable_set_importer (GstClapperPaintable *paintable, GstClapperImporter *importer);
gboolean gst_clapper_paintable_set_video_info (GstClapperPaintable *paintable, const GstVideoInfo *v_info);
void gst_clapper_paintable_set_pixel_aspect_ratio (GstClapperPaintable *paintable, gint par_n, gint par_d);
void gst_clapper_paintable_set_rotation (GstClapperPaintable *paintable, GstVideoOrientationMethod rotation);
GstVideoOrientationMethod gst_clapper_paintable_get_rotation (GstClapperPaintable *paintable);
G_END_DECLS

View File

@@ -0,0 +1,957 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstclappersink.h"
#include "gstgtkutils.h"
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
#define DEFAULT_PAR_N 1
#define DEFAULT_PAR_D 1
#define DEFAULT_KEEP_LAST_FRAME FALSE
#define DEFAULT_ROTATION GST_VIDEO_ORIENTATION_AUTO
#define WINDOW_CSS_CLASS_NAME "clappersinkwindow"
enum
{
PROP_0,
PROP_WIDGET,
PROP_FORCE_ASPECT_RATIO,
PROP_PIXEL_ASPECT_RATIO,
PROP_KEEP_LAST_FRAME,
PROP_ROTATE_METHOD,
PROP_LAST
};
#define GST_CAT_DEFAULT gst_clapper_sink_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
static void gst_clapper_sink_navigation_interface_init (
GstNavigationInterface *iface);
#define parent_class gst_clapper_sink_parent_class
G_DEFINE_TYPE_WITH_CODE (GstClapperSink, gst_clapper_sink, GST_TYPE_VIDEO_SINK,
G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
gst_clapper_sink_navigation_interface_init));
GST_ELEMENT_REGISTER_DEFINE (clappersink, "clappersink", GST_RANK_NONE,
GST_TYPE_CLAPPER_SINK);
static void
window_clear_no_lock (GstClapperSink *self)
{
if (!self->window)
return;
GST_TRACE_OBJECT (self, "Window clear");
if (self->window_destroy_id) {
g_signal_handler_disconnect (self->window, self->window_destroy_id);
self->window_destroy_id = 0;
}
self->window = NULL;
}
static void
widget_clear_no_lock (GstClapperSink *self)
{
if (!self->widget)
return;
GST_TRACE_OBJECT (self, "Widget clear");
if (self->widget_destroy_id) {
g_signal_handler_disconnect (self->widget, self->widget_destroy_id);
self->widget_destroy_id = 0;
}
g_clear_object (&self->widget);
}
static void
widget_destroy_cb (GtkWidget *widget, GstClapperSink *self)
{
GST_CLAPPER_SINK_LOCK (self);
widget_clear_no_lock (self);
GST_CLAPPER_SINK_UNLOCK (self);
}
static void
window_destroy_cb (GtkWidget *window, GstClapperSink *self)
{
GST_DEBUG_OBJECT (self, "Window destroy");
GST_CLAPPER_SINK_LOCK (self);
widget_clear_no_lock (self);
window_clear_no_lock (self);
GST_CLAPPER_SINK_UNLOCK (self);
}
static void
calculate_stream_coords (GstClapperSink *self, GtkWidget *widget,
gdouble x, gdouble y, gdouble *stream_x, gdouble *stream_y)
{
GstVideoRectangle result;
gint scaled_width, scaled_height, scale_factor;
gint video_width, video_height;
gboolean force_aspect_ratio;
GST_CLAPPER_SINK_LOCK (self);
gst_gtk_get_width_height_for_rotation (GST_VIDEO_INFO_WIDTH (&self->v_info),
GST_VIDEO_INFO_HEIGHT (&self->v_info), &video_height, &video_width,
gst_clapper_paintable_get_rotation (self->paintable));
force_aspect_ratio = self->force_aspect_ratio;
GST_CLAPPER_SINK_UNLOCK (self);
scale_factor = gtk_widget_get_scale_factor (widget);
scaled_width = gtk_widget_get_width (widget) * scale_factor;
scaled_height = gtk_widget_get_height (widget) * scale_factor;
if (force_aspect_ratio) {
GstVideoRectangle src, dst;
src.x = 0;
src.y = 0;
src.w = gdk_paintable_get_intrinsic_width ((GdkPaintable *) self->paintable);
src.h = gdk_paintable_get_intrinsic_height ((GdkPaintable *) self->paintable);
dst.x = 0;
dst.y = 0;
dst.w = scaled_width;
dst.h = scaled_height;
gst_video_center_rect (&src, &dst, &result, TRUE);
} else {
result.x = 0;
result.y = 0;
result.w = scaled_width;
result.h = scaled_height;
}
/* Display coordinates to stream coordinates */
*stream_x = (result.w > 0)
? (x - result.x) / result.w * video_width
: 0;
*stream_y = (result.h > 0)
? (y - result.y) / result.h * video_height
: 0;
/* Clip to stream size */
*stream_x = CLAMP (*stream_x, 0, video_width);
*stream_y = CLAMP (*stream_y, 0, video_height);
GST_LOG ("Transform coords %fx%f => %fx%f", x, y, *stream_x, *stream_y);
}
static void
gst_clapper_sink_widget_motion_event (GtkEventControllerMotion *motion,
gdouble x, gdouble y, GstClapperSink *self)
{
GtkWidget *widget;
gdouble stream_x, stream_y;
gboolean is_inactive;
if (x == self->last_pos_x && y == self->last_pos_y)
return;
GST_OBJECT_LOCK (self);
is_inactive = (GST_STATE (self) < GST_STATE_PLAYING);
GST_OBJECT_UNLOCK (self);
if (is_inactive)
return;
self->last_pos_x = x;
self->last_pos_y = y;
widget = gtk_event_controller_get_widget ((GtkEventController *) motion);
calculate_stream_coords (self, widget, x, y, &stream_x, &stream_y);
GST_LOG ("Event \"mouse-move\", x: %f, y: %f", stream_x, stream_y);
gst_navigation_send_mouse_event ((GstNavigation *) self, "mouse-move",
0, stream_x, stream_y);
}
static void
gst_clapper_sink_widget_button_event (GtkGestureClick *click,
gint n_press, gdouble x, gdouble y, GstClapperSink *self)
{
GtkWidget *widget;
GdkEvent *event;
GdkEventType event_type;
const gchar *event_name;
gdouble stream_x, stream_y;
gboolean is_inactive;
GST_OBJECT_LOCK (self);
is_inactive = (GST_STATE (self) < GST_STATE_PLAYING);
GST_OBJECT_UNLOCK (self);
if (is_inactive)
return;
event = gtk_event_controller_get_current_event ((GtkEventController *) click);
event_type = gdk_event_get_event_type (event);
/* FIXME: Touchscreen handling should probably use new touch events from GStreamer 1.22 */
event_name = (event_type == GDK_BUTTON_PRESS || event_type == GDK_TOUCH_BEGIN)
? "mouse-button-press"
: (event_type == GDK_BUTTON_RELEASE || event_type == GDK_TOUCH_END)
? "mouse-button-release"
: NULL;
/* Can be NULL on touch */
if (!event_name)
return;
widget = gtk_event_controller_get_widget ((GtkEventController *) click);
calculate_stream_coords (self, widget, x, y, &stream_x, &stream_y);
GST_LOG ("Event \"%s\", x: %f, y: %f", event_name, stream_x, stream_y);
/* Gesture is set to handle only primary button, so we do not have to check */
gst_navigation_send_mouse_event ((GstNavigation *) self, event_name,
1, stream_x, stream_y);
}
/* Must call from main thread only with a lock */
static GtkWidget *
gst_clapper_sink_get_widget (GstClapperSink *self)
{
if (G_UNLIKELY (!self->widget)) {
GtkEventController *controller;
GtkGesture *gesture;
/* Make sure GTK is initialized */
if (!gtk_init_check ()) {
GST_ERROR_OBJECT (self, "Could not ensure GTK initialization");
return NULL;
}
self->widget = gtk_picture_new ();
/* Otherwise widget in grid will appear as a 1x1px
* video which might be misleading for users */
gtk_widget_set_hexpand (self->widget, TRUE);
gtk_widget_set_vexpand (self->widget, TRUE);
gtk_widget_set_focusable (self->widget, TRUE);
gtk_widget_set_can_focus (self->widget, TRUE);
controller = gtk_event_controller_motion_new ();
g_signal_connect (controller, "motion",
G_CALLBACK (gst_clapper_sink_widget_motion_event), self);
gtk_widget_add_controller (self->widget, controller);
gesture = gtk_gesture_click_new ();
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 1);
g_signal_connect (gesture, "pressed",
G_CALLBACK (gst_clapper_sink_widget_button_event), self);
g_signal_connect (gesture, "released",
G_CALLBACK (gst_clapper_sink_widget_button_event), self);
gtk_widget_add_controller (self->widget, GTK_EVENT_CONTROLLER (gesture));
/* TODO: Implement touch events once we depend on GStreamer 1.22 */
/* Take floating ref */
g_object_ref_sink (self->widget);
/* Set widget back pointer */
gst_clapper_paintable_set_widget (self->paintable, self->widget);
/* Set earlier remembered property */
#if GTK_CHECK_VERSION(4,8,0)
if (self->force_aspect_ratio)
gtk_picture_set_content_fit (GTK_PICTURE (self->widget), GTK_CONTENT_FIT_CONTAIN);
else
gtk_picture_set_content_fit (GTK_PICTURE (self->widget), GTK_CONTENT_FIT_FILL);
#else
gtk_picture_set_keep_aspect_ratio (GTK_PICTURE (self->widget),
self->force_aspect_ratio);
#endif
gtk_picture_set_paintable (GTK_PICTURE (self->widget), GDK_PAINTABLE (self->paintable));
self->widget_destroy_id = g_signal_connect (self->widget,
"destroy", G_CALLBACK (widget_destroy_cb), self);
}
return self->widget;
}
static GtkWidget *
gst_clapper_sink_obtain_widget (GstClapperSink *self)
{
GtkWidget *widget;
GST_CLAPPER_SINK_LOCK (self);
widget = gst_clapper_sink_get_widget (self);
if (widget)
g_object_ref (widget);
GST_CLAPPER_SINK_UNLOCK (self);
return widget;
}
static void
gst_clapper_sink_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (object);
GST_CLAPPER_SINK_LOCK (self);
switch (prop_id) {
case PROP_WIDGET:
if (self->widget) {
g_value_set_object (value, self->widget);
} else {
GtkWidget *widget;
GST_CLAPPER_SINK_UNLOCK (self);
widget = gst_gtk_invoke_on_main ((GThreadFunc) gst_clapper_sink_obtain_widget, self);
GST_CLAPPER_SINK_LOCK (self);
g_value_set_object (value, widget);
g_object_unref (widget);
}
break;
case PROP_FORCE_ASPECT_RATIO:
g_value_set_boolean (value, self->force_aspect_ratio);
break;
case PROP_PIXEL_ASPECT_RATIO:
gst_value_set_fraction (value, self->par_n, self->par_d);
break;
case PROP_KEEP_LAST_FRAME:
g_value_set_boolean (value, self->keep_last_frame);
break;
case PROP_ROTATE_METHOD:
g_value_set_enum (value, self->rotation_mode);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
GST_CLAPPER_SINK_UNLOCK (self);
}
static void
gst_clapper_sink_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (object);
GST_CLAPPER_SINK_LOCK (self);
switch (prop_id) {
case PROP_FORCE_ASPECT_RATIO:
self->force_aspect_ratio = g_value_get_boolean (value);
if (self->widget) {
#if GTK_CHECK_VERSION(4,8,0)
if (self->force_aspect_ratio)
gtk_picture_set_content_fit (GTK_PICTURE (self->widget), GTK_CONTENT_FIT_CONTAIN);
else
gtk_picture_set_content_fit (GTK_PICTURE (self->widget), GTK_CONTENT_FIT_FILL);
#else
gtk_picture_set_keep_aspect_ratio (GTK_PICTURE (self->widget),
self->force_aspect_ratio);
#endif
}
break;
case PROP_PIXEL_ASPECT_RATIO:
self->par_n = gst_value_get_fraction_numerator (value);
self->par_d = gst_value_get_fraction_denominator (value);
gst_clapper_paintable_set_pixel_aspect_ratio (self->paintable,
self->par_n, self->par_d);
break;
case PROP_KEEP_LAST_FRAME:
self->keep_last_frame = g_value_get_boolean (value);
break;
case PROP_ROTATE_METHOD:
self->rotation_mode = g_value_get_enum (value);
gst_clapper_paintable_set_rotation (self->paintable,
(self->rotation_mode == GST_VIDEO_ORIENTATION_AUTO) ?
self->stream_orientation : self->rotation_mode);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
GST_CLAPPER_SINK_UNLOCK (self);
}
static void
gst_clapper_sink_navigation_send_event (GstNavigation *navigation,
GstStructure *structure)
{
GstClapperSink *sink = GST_CLAPPER_SINK_CAST (navigation);
GstEvent *event;
GST_TRACE_OBJECT (sink, "Navigation event: %" GST_PTR_FORMAT, structure);
event = gst_event_new_navigation (structure);
if (G_LIKELY (event)) {
GstPad *pad;
pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink));
if (G_LIKELY (pad)) {
if (!gst_pad_send_event (pad, gst_event_ref (event))) {
/* If upstream didn't handle the event we'll post a message with it
* for the application in case it wants to do something with it */
gst_element_post_message (GST_ELEMENT_CAST (sink),
gst_navigation_message_new_event (GST_OBJECT_CAST (sink), event));
}
gst_object_unref (pad);
}
gst_event_unref (event);
}
}
static gboolean
gst_clapper_sink_propose_allocation (GstBaseSink *bsink, GstQuery *query)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
GstClapperImporter *importer = NULL;
GstCaps *caps;
GstVideoInfo info;
guint size, min_buffers;
gboolean need_pool;
gst_query_parse_allocation (query, &caps, &need_pool);
if (!caps) {
GST_DEBUG_OBJECT (self, "No caps specified");
return FALSE;
}
if (!gst_video_info_from_caps (&info, caps)) {
GST_DEBUG_OBJECT (self, "Invalid caps specified");
return FALSE;
}
GST_CLAPPER_SINK_LOCK (self);
if (self->importer)
importer = gst_object_ref (self->importer);
GST_CLAPPER_SINK_UNLOCK (self);
if (!importer) {
GST_DEBUG_OBJECT (self, "No importer to propose allocation");
return FALSE;
}
/* Normal size of a frame */
size = GST_VIDEO_INFO_SIZE (&info);
/* We keep around current buffer and a pending one */
min_buffers = 3;
if (need_pool) {
GstBufferPool *pool;
GstStructure *config = NULL;
GST_DEBUG_OBJECT (self, "Need to create buffer pool");
pool = gst_clapper_importer_create_pool (importer, &config);
if (pool) {
/* If we did not get config, use default one */
if (!config)
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_set_params (config, caps, size, min_buffers, 0);
if (!gst_buffer_pool_set_config (pool, config)) {
gst_object_unref (pool);
gst_object_unref (importer);
GST_ERROR_OBJECT (self, "Failed to set config");
return FALSE;
}
gst_query_add_allocation_pool (query, pool, size, min_buffers, 0);
gst_object_unref (pool);
} else if (config) {
GST_WARNING_OBJECT (self, "Got config without a pool to apply it");
gst_structure_free (config);
}
}
gst_clapper_importer_add_allocation_metas (importer, query);
gst_object_unref (importer);
return TRUE;
}
static gboolean
gst_clapper_sink_query (GstBaseSink *bsink, GstQuery *query)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
gboolean res = FALSE;
if (GST_QUERY_TYPE (query) == GST_QUERY_CONTEXT) {
GST_CLAPPER_SINK_LOCK (self);
res = gst_clapper_importer_loader_handle_context_query (self->loader, bsink, query);
GST_CLAPPER_SINK_UNLOCK (self);
}
if (res)
return TRUE;
return GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
}
static gboolean
gst_clapper_sink_start_on_main (GstClapperSink *self)
{
GtkWidget *widget;
GST_CLAPPER_SINK_LOCK (self);
/* Make sure widget is created */
if (!(widget = gst_clapper_sink_get_widget (self))) {
GST_CLAPPER_SINK_UNLOCK (self);
return FALSE;
}
/* When no toplevel window, make our own */
if (G_UNLIKELY (!gtk_widget_get_root (widget) && !self->window)) {
GtkWidget *toplevel, *parent;
GtkCssProvider *provider;
gchar *win_title;
if ((parent = gtk_widget_get_parent (widget))) {
GtkWidget *temp_parent;
while ((temp_parent = gtk_widget_get_parent (parent)))
parent = temp_parent;
}
toplevel = (parent) ? parent : widget;
self->window = (GtkWindow *) gtk_window_new ();
gtk_widget_add_css_class (GTK_WIDGET (self->window), WINDOW_CSS_CLASS_NAME);
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider,
"." WINDOW_CSS_CLASS_NAME " { background: none; }", -1);
gtk_style_context_add_provider_for_display (
gdk_display_get_default (), GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref (provider);
win_title = g_strdup_printf ("Clapper Sink - GTK %u.%u.%u Window",
gtk_get_major_version (),
gtk_get_minor_version (),
gtk_get_micro_version ());
/* Set some common default size, adding stock headerbar height
* to it in order to display 4:3 aspect video widget */
gtk_window_set_default_size (self->window, 640, 480 + 37);
gtk_window_set_title (self->window, win_title);
gtk_window_set_child (self->window, toplevel);
g_free (win_title);
self->window_destroy_id = g_signal_connect (self->window,
"destroy", G_CALLBACK (window_destroy_cb), self);
GST_INFO_OBJECT (self, "Presenting window");
gtk_window_present (self->window);
}
GST_CLAPPER_SINK_UNLOCK (self);
return TRUE;
}
static gboolean
gst_clapper_sink_start (GstBaseSink *bsink)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
GST_INFO_OBJECT (self, "Start");
if (G_UNLIKELY (!(! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
gst_clapper_sink_start_on_main, self)))) {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("GtkWidget could not be created"), (NULL));
return FALSE;
}
return TRUE;
}
static gboolean
gst_clapper_sink_stop_on_main (GstClapperSink *self)
{
GtkWindow *window = NULL;
GST_CLAPPER_SINK_LOCK (self);
if (self->window)
window = g_object_ref (self->window);
GST_CLAPPER_SINK_UNLOCK (self);
if (window) {
gtk_window_destroy (window);
g_object_unref (window);
}
return TRUE;
}
static gboolean
gst_clapper_sink_stop (GstBaseSink *bsink)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
gboolean has_window;
GST_INFO_OBJECT (self, "Stop");
GST_CLAPPER_SINK_LOCK (self);
has_window = (self->window != NULL);
GST_CLAPPER_SINK_UNLOCK (self);
if (G_UNLIKELY (has_window)) {
return (! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
gst_clapper_sink_stop_on_main, self));
}
return TRUE;
}
static gboolean
gst_clapper_sink_event (GstBaseSink *bsink, GstEvent *event)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
GstTagList *taglist;
GstVideoOrientationMethod orientation;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_TAG:
gst_event_parse_tag (event, &taglist);
if (gst_video_orientation_from_tag (taglist, &orientation)) {
GST_CLAPPER_SINK_LOCK (self);
self->stream_orientation = orientation;
if (self->rotation_mode == GST_VIDEO_ORIENTATION_AUTO)
gst_clapper_paintable_set_rotation (self->paintable, orientation);
GST_CLAPPER_SINK_UNLOCK (self);
}
break;
default:
break;
}
return GST_BASE_SINK_CLASS (parent_class)->event (bsink, event);
}
static GstStateChangeReturn
gst_clapper_sink_change_state (GstElement *element, GstStateChange transition)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (element);
GST_DEBUG_OBJECT (self, "Changing state: %s => %s",
gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
/* Reset stream_orientation */
GST_CLAPPER_SINK_LOCK (self);
self->stream_orientation = GST_VIDEO_ORIENTATION_IDENTITY;
if (self->rotation_mode == GST_VIDEO_ORIENTATION_AUTO)
gst_clapper_paintable_set_rotation (self->paintable, self->stream_orientation);
GST_CLAPPER_SINK_UNLOCK (self);
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
GST_CLAPPER_SINK_LOCK (self);
if (!self->keep_last_frame && self->importer) {
gst_clapper_importer_set_buffer (self->importer, NULL);
gst_clapper_paintable_queue_draw (self->paintable);
}
GST_CLAPPER_SINK_UNLOCK (self);
break;
default:
break;
}
return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
}
static void
gst_clapper_sink_get_times (GstBaseSink *bsink, GstBuffer *buffer,
GstClockTime *start, GstClockTime *end)
{
if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
return;
*start = GST_BUFFER_TIMESTAMP (buffer);
if (GST_BUFFER_DURATION_IS_VALID (buffer)) {
*end = *start + GST_BUFFER_DURATION (buffer);
} else {
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
gint fps_n, fps_d;
GST_CLAPPER_SINK_LOCK (self);
fps_n = GST_VIDEO_INFO_FPS_N (&self->v_info);
fps_d = GST_VIDEO_INFO_FPS_D (&self->v_info);
GST_CLAPPER_SINK_UNLOCK (self);
if (fps_n > 0)
*end = *start + gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
}
}
static GstCaps *
gst_clapper_sink_get_caps (GstBaseSink *bsink, GstCaps *filter)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
GstCaps *result, *tmp;
tmp = gst_clapper_importer_loader_make_actual_caps (self->loader);
if (filter) {
GST_DEBUG ("Intersecting with filter caps: %" GST_PTR_FORMAT, filter);
result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (tmp);
} else {
result = tmp;
}
GST_DEBUG ("Returning caps: %" GST_PTR_FORMAT, result);
return result;
}
static gboolean
gst_clapper_sink_set_caps (GstBaseSink *bsink, GstCaps *caps)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
GST_INFO_OBJECT (self, "Set caps: %" GST_PTR_FORMAT, caps);
GST_CLAPPER_SINK_LOCK (self);
if (G_UNLIKELY (!self->widget)) {
GST_CLAPPER_SINK_UNLOCK (self);
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("Output widget was destroyed"), (NULL));
return FALSE;
}
if (!gst_clapper_importer_loader_find_importer_for_caps (self->loader, caps, &self->importer)) {
GST_CLAPPER_SINK_UNLOCK (self);
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("No importer for given caps found"), (NULL));
return FALSE;
}
gst_clapper_paintable_set_importer (self->paintable, self->importer);
GST_CLAPPER_SINK_UNLOCK (self);
return GST_BASE_SINK_CLASS (parent_class)->set_caps (bsink, caps);
}
static gboolean
gst_clapper_sink_set_info (GstVideoSink *vsink, GstCaps *caps, const GstVideoInfo *info)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (vsink);
gboolean res;
GST_CLAPPER_SINK_LOCK (self);
self->v_info = *info;
GST_DEBUG_OBJECT (self, "Video info changed");
res = gst_clapper_paintable_set_video_info (self->paintable, info);
GST_CLAPPER_SINK_UNLOCK (self);
return res;
}
static GstFlowReturn
gst_clapper_sink_show_frame (GstVideoSink *vsink, GstBuffer *buffer)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (vsink);
GST_TRACE ("Got %" GST_PTR_FORMAT, buffer);
GST_CLAPPER_SINK_LOCK (self);
if (G_UNLIKELY (!self->widget)) {
GST_CLAPPER_SINK_UNLOCK (self);
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("Output widget was destroyed"), (NULL));
return GST_FLOW_ERROR;
}
gst_clapper_importer_set_buffer (self->importer, buffer);
gst_clapper_paintable_queue_draw (self->paintable);
GST_CLAPPER_SINK_UNLOCK (self);
return GST_FLOW_OK;
}
static void
gst_clapper_sink_init (GstClapperSink *self)
{
GObjectClass *gobject_class;
gobject_class = (GObjectClass *) GST_CLAPPER_SINK_GET_CLASS (self);
/* HACK: install here instead of class init to avoid GStreamer
* plugin scanner GObject type conflicts with older GTK versions */
if (!g_object_class_find_property (gobject_class, "widget")) {
g_object_class_install_property (gobject_class, PROP_WIDGET,
g_param_spec_object ("widget", "GTK Widget",
"The GtkWidget to place in the widget hierarchy",
GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}
self->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
self->par_n = DEFAULT_PAR_N;
self->par_d = DEFAULT_PAR_D;
self->keep_last_frame = DEFAULT_KEEP_LAST_FRAME;
self->rotation_mode = DEFAULT_ROTATION;
g_mutex_init (&self->lock);
gst_video_info_init (&self->v_info);
self->paintable = gst_clapper_paintable_new ();
self->loader = gst_clapper_importer_loader_new ();
}
static void
gst_clapper_sink_dispose (GObject *object)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (object);
GST_CLAPPER_SINK_LOCK (self);
window_clear_no_lock (self);
widget_clear_no_lock (self);
g_clear_object (&self->paintable);
gst_clear_object (&self->importer);
GST_CLAPPER_SINK_UNLOCK (self);
GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
}
static void
gst_clapper_sink_finalize (GObject *object)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (object);
GST_TRACE ("Finalize");
gst_clear_object (&self->loader);
g_mutex_clear (&self->lock);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
gst_clapper_sink_class_init (GstClapperSinkClass *klass)
{
GstPadTemplate *sink_pad_templ;
GObjectClass *gobject_class = (GObjectClass *) klass;
GstElementClass *gstelement_class = (GstElementClass *) klass;
GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass;
GstVideoSinkClass *gstvideosink_class = (GstVideoSinkClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappersink", 0,
"Clapper Sink");
gobject_class->get_property = gst_clapper_sink_get_property;
gobject_class->set_property = gst_clapper_sink_set_property;
gobject_class->dispose = gst_clapper_sink_dispose;
gobject_class->finalize = gst_clapper_sink_finalize;
g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
"When enabled, scaling will respect original aspect ratio",
DEFAULT_FORCE_ASPECT_RATIO,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
"The pixel aspect ratio of the device",
DEFAULT_PAR_N, DEFAULT_PAR_D,
G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_KEEP_LAST_FRAME,
g_param_spec_boolean ("keep-last-frame", "Keep last frame",
"Keep showing last video frame after playback instead of black screen",
DEFAULT_KEEP_LAST_FRAME,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_ROTATE_METHOD,
g_param_spec_enum ("rotate-method", "Rotate Method",
"Rotate method to use",
GST_TYPE_VIDEO_ORIENTATION_METHOD, DEFAULT_ROTATION,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gstelement_class->change_state = gst_clapper_sink_change_state;
gstbasesink_class->get_caps = gst_clapper_sink_get_caps;
gstbasesink_class->set_caps = gst_clapper_sink_set_caps;
gstbasesink_class->get_times = gst_clapper_sink_get_times;
gstbasesink_class->propose_allocation = gst_clapper_sink_propose_allocation;
gstbasesink_class->query = gst_clapper_sink_query;
gstbasesink_class->start = gst_clapper_sink_start;
gstbasesink_class->stop = gst_clapper_sink_stop;
gstbasesink_class->event = gst_clapper_sink_event;
gstvideosink_class->set_info = gst_clapper_sink_set_info;
gstvideosink_class->show_frame = gst_clapper_sink_show_frame;
gst_element_class_set_static_metadata (gstelement_class,
"Clapper video sink",
"Sink/Video", "A GTK4 video sink used by Clapper media player",
"Rafał Dzięgiel <rafostar.github@gmail.com>");
sink_pad_templ = gst_clapper_importer_loader_make_sink_pad_template ();
gst_element_class_add_pad_template (gstelement_class, sink_pad_templ);
}
/*
* GstNavigationInterface
*/
static void
gst_clapper_sink_navigation_interface_init (GstNavigationInterface *iface)
{
/* TODO: Port to "send_event_simple" once we depend on GStreamer 1.22 */
iface->send_event = gst_clapper_sink_navigation_send_event;
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gtk/gtk.h>
#include <gst/gst.h>
#include <gst/video/gstvideosink.h>
#include <gst/video/video.h>
#include "gstclapperpaintable.h"
#include "gstclapperimporterloader.h"
#include "gstclapperimporter.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_SINK (gst_clapper_sink_get_type())
G_DECLARE_FINAL_TYPE (GstClapperSink, gst_clapper_sink, GST, CLAPPER_SINK, GstVideoSink)
#define GST_CLAPPER_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_SINK, GstClapperSinkClass))
#define GST_CLAPPER_SINK_CAST(obj) ((GstClapperSink *)(obj))
#define GST_CLAPPER_SINK_GET_LOCK(obj) (&GST_CLAPPER_SINK_CAST(obj)->lock)
#define GST_CLAPPER_SINK_LOCK(obj) g_mutex_lock (GST_CLAPPER_SINK_GET_LOCK(obj))
#define GST_CLAPPER_SINK_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_SINK_GET_LOCK(obj))
struct _GstClapperSink
{
GstVideoSink parent;
GMutex lock;
GstClapperPaintable *paintable;
GstClapperImporterLoader *loader;
GstClapperImporter *importer;
GstVideoInfo v_info;
GstVideoOrientationMethod stream_orientation;
GtkWidget *widget;
GtkWindow *window;
/* Properties */
gboolean force_aspect_ratio;
gint par_n, par_d;
gboolean keep_last_frame;
GstVideoOrientationMethod rotation_mode;
/* Position coords */
gdouble last_pos_x;
gdouble last_pos_y;
gulong widget_destroy_id;
gulong window_destroy_id;
};
GST_ELEMENT_REGISTER_DECLARE (clappersink);
G_END_DECLS

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <glib.h>
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define GST_GDK_MEMORY_ENDIAN_FORMATS "RGBA64_LE"
#define GST_GDK_GL_TEXTURE_ENDIAN_FORMATS "RGBA64_LE"
#elif G_BYTE_ORDER == G_BIG_ENDIAN
#define GST_GDK_MEMORY_ENDIAN_FORMATS "RGBA64_BE"
#define GST_GDK_GL_TEXTURE_ENDIAN_FORMATS "RGBA64_BE"
#endif
#define GST_GDK_MEMORY_FORMATS \
GST_GDK_MEMORY_ENDIAN_FORMATS ", " \
"ABGR, BGRA, ARGB, RGBA, BGRx, RGBx, BGR, RGB"
/* Formats that `GdkGLTexture` supports */
#define GST_GDK_GL_TEXTURE_FORMATS \
GST_GDK_GL_TEXTURE_ENDIAN_FORMATS ", " \
"RGBA, RGBx, RGB"

View File

@@ -0,0 +1,162 @@
/*
* GStreamer
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "gstgtkutils.h"
#define _IS_FRAME_PREMULTIPLIED(f) (GST_VIDEO_INFO_FLAG_IS_SET (&(f)->info, GST_VIDEO_FLAG_PREMULTIPLIED_ALPHA))
struct invoke_context
{
GThreadFunc func;
gpointer data;
GMutex lock;
GCond cond;
gboolean fired;
gpointer res;
};
static gboolean
gst_gtk_invoke_func (struct invoke_context *info)
{
g_mutex_lock (&info->lock);
info->res = info->func (info->data);
info->fired = TRUE;
g_cond_signal (&info->cond);
g_mutex_unlock (&info->lock);
return G_SOURCE_REMOVE;
}
gpointer
gst_gtk_invoke_on_main (GThreadFunc func, gpointer data)
{
GMainContext *main_context = g_main_context_default ();
struct invoke_context info;
g_mutex_init (&info.lock);
g_cond_init (&info.cond);
info.fired = FALSE;
info.func = func;
info.data = data;
g_main_context_invoke (main_context, (GSourceFunc) gst_gtk_invoke_func,
&info);
g_mutex_lock (&info.lock);
while (!info.fired)
g_cond_wait (&info.cond, &info.lock);
g_mutex_unlock (&info.lock);
g_mutex_clear (&info.lock);
g_cond_clear (&info.cond);
return info.res;
}
static GdkMemoryFormat
gst_gdk_memory_format_from_frame (GstVideoFrame *frame)
{
switch (GST_VIDEO_FRAME_FORMAT (frame)) {
case GST_VIDEO_FORMAT_RGBA64_LE:
case GST_VIDEO_FORMAT_RGBA64_BE:
return (_IS_FRAME_PREMULTIPLIED (frame))
? GDK_MEMORY_R16G16B16A16_PREMULTIPLIED
: GDK_MEMORY_R16G16B16A16;
case GST_VIDEO_FORMAT_RGBA:
return (_IS_FRAME_PREMULTIPLIED (frame))
? GDK_MEMORY_R8G8B8A8_PREMULTIPLIED
: GDK_MEMORY_R8G8B8A8;
case GST_VIDEO_FORMAT_BGRA:
return (_IS_FRAME_PREMULTIPLIED (frame))
? GDK_MEMORY_B8G8R8A8_PREMULTIPLIED
: GDK_MEMORY_B8G8R8A8;
case GST_VIDEO_FORMAT_ARGB:
return (_IS_FRAME_PREMULTIPLIED (frame))
? GDK_MEMORY_A8R8G8B8_PREMULTIPLIED
: GDK_MEMORY_A8R8G8B8;
case GST_VIDEO_FORMAT_ABGR:
/* GTK is missing premultiplied ABGR support */
return GDK_MEMORY_A8B8G8R8;
case GST_VIDEO_FORMAT_RGBx:
return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
case GST_VIDEO_FORMAT_BGRx:
return GDK_MEMORY_B8G8R8A8_PREMULTIPLIED;
case GST_VIDEO_FORMAT_RGB:
return GDK_MEMORY_R8G8B8;
case GST_VIDEO_FORMAT_BGR:
return GDK_MEMORY_B8G8R8;
default:
break;
}
/* This should never happen as long as above switch statement
* is updated when new formats are added to caps */
g_assert_not_reached ();
/* Fallback format */
return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
}
GdkTexture *
gst_video_frame_into_gdk_texture (GstVideoFrame *frame)
{
GdkTexture *texture;
GBytes *bytes;
bytes = g_bytes_new_with_free_func (
GST_VIDEO_FRAME_PLANE_DATA (frame, 0),
GST_VIDEO_FRAME_HEIGHT (frame) * GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0),
(GDestroyNotify) gst_buffer_unref,
gst_buffer_ref (frame->buffer));
texture = gdk_memory_texture_new (
GST_VIDEO_FRAME_WIDTH (frame),
GST_VIDEO_FRAME_HEIGHT (frame),
gst_gdk_memory_format_from_frame (frame),
bytes,
GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0));
g_bytes_unref (bytes);
return texture;
}
void
gst_gtk_get_width_height_for_rotation (gint width, gint height,
gint *out_width, gint *out_height,
GstVideoOrientationMethod rotation)
{
switch (rotation) {
case GST_VIDEO_ORIENTATION_90R:
case GST_VIDEO_ORIENTATION_90L:
case GST_VIDEO_ORIENTATION_UL_LR:
case GST_VIDEO_ORIENTATION_UR_LL:
*out_width = height;
*out_height = width;
break;
default:
*out_width = width;
*out_height = height;
break;
}
}

View File

@@ -0,0 +1,39 @@
/*
* GStreamer
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gtk/gtk.h>
#include <gst/video/video.h>
G_BEGIN_DECLS
gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data);
GdkTexture * gst_video_frame_into_gdk_texture (GstVideoFrame *frame);
void gst_gtk_get_width_height_for_rotation (gint width, gint height,
gint *out_width, gint *out_height,
GstVideoOrientationMethod rotation);
G_END_DECLS

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gmodule.h>
#include "gstclappersink.h"
static gboolean
plugin_init (GstPlugin *plugin)
{
if (!g_module_supported ())
return FALSE;
gst_plugin_add_dependency_simple (plugin,
NULL, CLAPPER_SINK_IMPORTER_PATH, NULL,
GST_PLUGIN_DEPENDENCY_FLAG_NONE);
return GST_ELEMENT_REGISTER (clappersink, plugin);
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
clapper, "Clapper elements", plugin_init, VERSION, "LGPL",
GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)

View File

@@ -0,0 +1,583 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstclapperglcontexthandler.h"
#include "gst/plugin/gstgdkformats.h"
#include "gst/plugin/gstgtkutils.h"
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WAYLAND
#include <gdk/wayland/gdkwayland.h>
#include <gst/gl/wayland/gstgldisplay_wayland.h>
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11
#include <gdk/x11/gdkx.h>
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_GLX
#include <gst/gl/x11/gstgldisplay_x11.h>
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_EGL || GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_EGL
#include <gst/gl/egl/gstgldisplay_egl.h>
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32
#include <gdk/win32/gdkwin32.h>
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_MACOS
#include <gdk/macos/gdkmacos.h>
#endif
#define GST_CAT_DEFAULT gst_clapper_gl_context_handler_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define parent_class gst_clapper_gl_context_handler_parent_class
G_DEFINE_TYPE (GstClapperGLContextHandler, gst_clapper_gl_context_handler, GST_TYPE_OBJECT);
static GstGLContext *
_wrap_current_gl (GstGLDisplay *display, GdkGLAPI gdk_gl_api, GstGLPlatform platform)
{
GstGLAPI gst_gl_api = GST_GL_API_NONE;
switch (gdk_gl_api) {
case GDK_GL_API_GL:
gst_gl_api = GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
break;
case GDK_GL_API_GLES:
gst_gl_api = GST_GL_API_GLES2;
break;
default:
g_assert_not_reached ();
break;
}
if (gst_gl_api != GST_GL_API_NONE) {
guintptr gl_handle;
gst_gl_display_filter_gl_api (display, gst_gl_api);
if ((gl_handle = gst_gl_context_get_current_gl_context (platform)))
return gst_gl_context_new_wrapped (display, gl_handle, platform, gst_gl_api);
}
return NULL;
}
static gboolean
_realize_gdk_context_with_api (GdkGLContext *gdk_context, GdkGLAPI api, gint maj, gint min)
{
GError *error = NULL;
gboolean success;
gdk_gl_context_set_allowed_apis (gdk_context, api);
gdk_gl_context_set_required_version (gdk_context, maj, min);
GST_DEBUG ("Trying to realize %s context, min ver: %i.%i",
(api & GDK_GL_API_GL) ? "GL" : "GLES", maj, min);
if (!(success = gdk_gl_context_realize (gdk_context, &error))) {
GST_DEBUG ("Could not realize Gdk context with %s: %s",
(api & GDK_GL_API_GL) ? "GL" : "GLES", error->message);
g_clear_error (&error);
}
return success;
}
static gboolean
_gl_context_handler_context_realize (GstClapperGLContextHandler *self, GdkGLContext *gdk_context)
{
GdkGLAPI preferred_api = GDK_GL_API_GL;
GdkDisplay *gdk_display;
const gchar *gl_env;
gboolean success;
GST_DEBUG_OBJECT (self, "Realizing GdkGLContext with default implementation");
/* Use single "GST_GL_API" env to also influence Gdk GL selection */
if ((gl_env = g_getenv ("GST_GL_API"))) {
preferred_api = (g_str_has_prefix (gl_env, "gles"))
? GDK_GL_API_GLES
: g_str_has_prefix (gl_env, "opengl")
? GDK_GL_API_GL
: GDK_GL_API_GL | GDK_GL_API_GLES;
/* With requested by user API, we either use it or give up */
return _realize_gdk_context_with_api (gdk_context, preferred_api, 0, 0);
}
gdk_display = gdk_gl_context_get_display (gdk_context);
GST_DEBUG_OBJECT (self, "Auto selecting GL API for display: %s",
gdk_display_get_name (gdk_display));
/* Apple decoder uses rectangle texture-target, which GLES does not support.
* For Linux we prefer EGL + GLES in order to get direct HW colorspace conversion.
* Windows will try EGL + GLES setup first and auto fallback to WGL. */
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gdk_display))
preferred_api = GDK_GL_API_GLES;
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_EGL
if (GDK_IS_X11_DISPLAY (gdk_display) && gdk_x11_display_get_egl_display (gdk_display))
preferred_api = GDK_GL_API_GLES;
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_EGL
if (GDK_IS_WIN32_DISPLAY (gdk_display) && gdk_win32_display_get_egl_display (gdk_display))
preferred_api = GDK_GL_API_GLES;
#endif
/* FIXME: Remove once GStreamer can handle DRM modifiers. This tries to avoid
* "scrambled" image on Linux with Intel GPUs that are mostly used together with
* x86 CPUs at the expense of using slightly slower non-direct DMABuf import.
* See: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1236 */
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WAYLAND || GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_EGL
#if !defined(HAVE_GST_PATCHES) && (defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64))
preferred_api = GDK_GL_API_GL;
#endif
#endif
/* Continue with GLES only if it should have "GL_EXT_texture_norm16"
* extension, as we need it to handle P010_10LE, etc. */
if ((preferred_api == GDK_GL_API_GLES)
&& _realize_gdk_context_with_api (gdk_context, GDK_GL_API_GLES, 3, 1))
return TRUE;
/* If not using GLES 3.1, try with core GL 3.2 that GTK4 defaults to */
if (_realize_gdk_context_with_api (gdk_context, GDK_GL_API_GL, 3, 2))
return TRUE;
/* Try with what we normally prefer first, otherwise use fallback */
if (!(success = _realize_gdk_context_with_api (gdk_context, preferred_api, 0, 0))) {
GdkGLAPI fallback_api;
fallback_api = (GDK_GL_API_GL | GDK_GL_API_GLES);
fallback_api &= ~preferred_api;
success = _realize_gdk_context_with_api (gdk_context, fallback_api, 0, 0);
}
return success;
}
static gboolean
_retrieve_gl_context_on_main (GstClapperGLContextHandler *self)
{
GdkDisplay *gdk_display;
GdkGLContext *gdk_context;
GError *error = NULL;
GdkGLAPI gdk_gl_api;
GstGLPlatform platform = GST_GL_PLATFORM_NONE;
gint gl_major = 0, gl_minor = 0;
if (!gtk_init_check ()) {
GST_ERROR_OBJECT (self, "Could not ensure GTK initialization");
return FALSE;
}
gdk_display = gdk_display_get_default ();
if (G_UNLIKELY (!gdk_display)) {
GST_ERROR_OBJECT (self, "Could not retrieve Gdk display");
return FALSE;
}
if (!(gdk_context = gdk_display_create_gl_context (gdk_display, &error))) {
GST_ERROR_OBJECT (self, "Error creating Gdk GL context: %s",
error ? error->message : "No error set by Gdk");
g_clear_error (&error);
return FALSE;
}
if (!_gl_context_handler_context_realize (self, gdk_context)) {
GST_ERROR_OBJECT (self, "Could not realize Gdk context: %" GST_PTR_FORMAT,
gdk_context);
g_object_unref (gdk_context);
return FALSE;
}
gdk_gl_api = gdk_gl_context_get_api (gdk_context);
GST_OBJECT_LOCK (self);
self->gdk_context = gdk_context;
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) {
struct wl_display *wayland_display =
gdk_wayland_display_get_wl_display (gdk_display);
self->gst_display = (GstGLDisplay *)
gst_gl_display_wayland_new_with_display (wayland_display);
}
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11
if (GDK_IS_X11_DISPLAY (gdk_display)) {
gpointer display_ptr;
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_EGL
display_ptr = gdk_x11_display_get_egl_display (gdk_display);
if (display_ptr) {
self->gst_display = (GstGLDisplay *)
gst_gl_display_egl_new_with_egl_display (display_ptr);
}
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_GLX
if (!self->gst_display) {
display_ptr = gdk_x11_display_get_xdisplay (gdk_display);
self->gst_display = (GstGLDisplay *)
gst_gl_display_x11_new_with_display (display_ptr);
}
}
#endif
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32
if (GDK_IS_WIN32_DISPLAY (gdk_display)) {
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_EGL
gpointer display_ptr = gdk_win32_display_get_egl_display (gdk_display);
if (display_ptr) {
self->gst_display = (GstGLDisplay *)
gst_gl_display_egl_new_with_egl_display (display_ptr);
}
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_WGL
if (!self->gst_display) {
self->gst_display =
gst_gl_display_new_with_type (GST_GL_DISPLAY_TYPE_WIN32);
}
}
#endif
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_MACOS
if (GDK_IS_MACOS_DISPLAY (gdk_display)) {
self->gst_display =
gst_gl_display_new_with_type (GST_GL_DISPLAY_TYPE_COCOA);
}
#endif
/* Fallback to generic display */
if (G_UNLIKELY (!self->gst_display)) {
GST_WARNING_OBJECT (self, "Unknown Gdk display!");
self->gst_display = gst_gl_display_new ();
}
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WAYLAND
if (GST_IS_GL_DISPLAY_WAYLAND (self->gst_display)) {
platform = GST_GL_PLATFORM_EGL;
GST_INFO_OBJECT (self, "Using EGL on Wayland");
goto have_display;
}
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_EGL
if (GST_IS_GL_DISPLAY_EGL (self->gst_display)
&& GDK_IS_X11_DISPLAY (gdk_display)) {
platform = GST_GL_PLATFORM_EGL;
GST_INFO_OBJECT (self, "Using EGL on x11");
goto have_display;
}
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_GLX
if (GST_IS_GL_DISPLAY_X11 (self->gst_display)) {
platform = GST_GL_PLATFORM_GLX;
GST_INFO_OBJECT (self, "Using GLX on x11");
goto have_display;
}
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_EGL
if (GST_IS_GL_DISPLAY_EGL (self->gst_display)
&& GDK_IS_WIN32_DISPLAY (gdk_display)) {
platform = GST_GL_PLATFORM_EGL;
GST_INFO_OBJECT (self, "Using EGL on Win32");
goto have_display;
}
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_WGL
if (gst_gl_display_get_handle_type (self->gst_display) == GST_GL_DISPLAY_TYPE_WIN32) {
platform = GST_GL_PLATFORM_WGL;
GST_INFO_OBJECT (self, "Using WGL on Win32");
goto have_display;
}
#endif
#if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_MACOS
if (gst_gl_display_get_handle_type (self->gst_display) == GST_GL_DISPLAY_TYPE_COCOA) {
platform = GST_GL_PLATFORM_CGL;
GST_INFO_OBJECT (self, "Using CGL on macOS");
goto have_display;
}
#endif
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_display);
GST_OBJECT_UNLOCK (self);
GST_ERROR_OBJECT (self, "Unsupported GL platform");
return FALSE;
have_display:
gdk_gl_context_make_current (self->gdk_context);
self->wrapped_context = _wrap_current_gl (self->gst_display, gdk_gl_api, platform);
if (!self->wrapped_context) {
GST_ERROR ("Could not retrieve Gdk OpenGL context");
gdk_gl_context_clear_current ();
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_display);
GST_OBJECT_UNLOCK (self);
return FALSE;
}
GST_INFO ("Retrieved Gdk OpenGL context %" GST_PTR_FORMAT, self->wrapped_context);
gst_gl_context_activate (self->wrapped_context, TRUE);
if (!gst_gl_context_fill_info (self->wrapped_context, &error)) {
GST_ERROR ("Failed to fill Gdk context info: %s", error->message);
g_clear_error (&error);
gst_gl_context_activate (self->wrapped_context, FALSE);
gst_clear_object (&self->wrapped_context);
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_display);
GST_OBJECT_UNLOCK (self);
return FALSE;
}
gst_gl_context_get_gl_version (self->wrapped_context, &gl_major, &gl_minor);
GST_INFO ("Using OpenGL%s %i.%i", (gdk_gl_api == GDK_GL_API_GLES) ? " ES" : "",
gl_major, gl_minor);
/* Deactivate in both places */
gst_gl_context_activate (self->wrapped_context, FALSE);
gdk_gl_context_clear_current ();
GST_OBJECT_UNLOCK (self);
return TRUE;
}
static gboolean
_retrieve_gst_context (GstClapperGLContextHandler *self)
{
GstGLDisplay *gst_display = NULL;
GstGLContext *gst_context = NULL;
GError *error = NULL;
GST_OBJECT_LOCK (self);
gst_display = gst_object_ref (self->gst_display);
GST_TRACE_OBJECT (self, "Creating new GstGLContext");
/* GstGLDisplay operations require display object lock to be held */
GST_OBJECT_LOCK (gst_display);
if (!gst_gl_display_create_context (gst_display, self->wrapped_context,
&self->gst_context, &error)) {
GST_WARNING ("Could not create OpenGL context: %s",
error ? error->message : "Unknown");
g_clear_error (&error);
GST_OBJECT_UNLOCK (gst_display);
GST_OBJECT_UNLOCK (self);
return FALSE;
}
gst_context = gst_object_ref (self->gst_context);
GST_OBJECT_UNLOCK (self);
gst_gl_display_add_context (gst_display, gst_context);
GST_OBJECT_UNLOCK (gst_display);
gst_object_unref (gst_display);
gst_object_unref (gst_context);
return TRUE;
}
static gboolean
gst_clapper_gl_context_handler_handle_context_query (GstClapperContextHandler *handler,
GstBaseSink *bsink, GstQuery *query)
{
GstClapperGLContextHandler *self = GST_CLAPPER_GL_CONTEXT_HANDLER_CAST (handler);
gboolean res;
GST_OBJECT_LOCK (self);
res = gst_gl_handle_context_query (GST_ELEMENT_CAST (bsink), query,
self->gst_display, self->gst_context, self->wrapped_context);
GST_OBJECT_UNLOCK (self);
return res;
}
static void
gst_clapper_gl_context_handler_init (GstClapperGLContextHandler *self)
{
}
static void
gst_clapper_gl_context_handler_constructed (GObject *object)
{
GstClapperGLContextHandler *self = GST_CLAPPER_GL_CONTEXT_HANDLER_CAST (object);
if (! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
_retrieve_gl_context_on_main, self)) {
_retrieve_gst_context (self);
}
GST_CALL_PARENT (G_OBJECT_CLASS, constructed, (object));
}
static void
gst_clapper_gl_context_handler_finalize (GObject *object)
{
GstClapperGLContextHandler *self = GST_CLAPPER_GL_CONTEXT_HANDLER_CAST (object);
GST_TRACE ("Finalize");
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_display);
gst_clear_object (&self->wrapped_context);
gst_clear_object (&self->gst_context);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
gst_clapper_gl_context_handler_class_init (GstClapperGLContextHandlerClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstClapperContextHandlerClass *handler_class = (GstClapperContextHandlerClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperglcontexthandler", 0,
"Clapper GL Context Handler");
gobject_class->constructed = gst_clapper_gl_context_handler_constructed;
gobject_class->finalize = gst_clapper_gl_context_handler_finalize;
handler_class->handle_context_query = gst_clapper_gl_context_handler_handle_context_query;
}
void
gst_clapper_gl_context_handler_add_handler (GPtrArray *context_handlers)
{
guint i;
gboolean found = FALSE;
for (i = 0; i < context_handlers->len; i++) {
GstClapperContextHandler *handler = g_ptr_array_index (context_handlers, i);
if ((found = GST_IS_CLAPPER_GL_CONTEXT_HANDLER (handler)))
break;
}
if (!found) {
GstClapperGLContextHandler *handler = g_object_new (
GST_TYPE_CLAPPER_GL_CONTEXT_HANDLER, NULL);
g_ptr_array_add (context_handlers, handler);
GST_DEBUG ("Added GL context handler to handlers array");
}
}
GstCaps *
gst_clapper_gl_context_handler_make_gdk_gl_caps (const gchar *features, gboolean only_2d)
{
GstCaps *caps, *tmp;
if (only_2d) {
tmp = gst_caps_from_string (GST_VIDEO_CAPS_MAKE (
"{ " GST_GDK_GL_TEXTURE_FORMATS " }") ", "
"texture-target = (string) { " GST_GL_TEXTURE_TARGET_2D_STR " }");
} else {
tmp = gst_caps_from_string (GST_VIDEO_CAPS_MAKE (
"{ " GST_GDK_GL_TEXTURE_FORMATS " }"));
}
caps = gst_caps_copy (tmp);
gst_caps_set_features_simple (tmp, gst_caps_features_new (
features, NULL));
gst_caps_set_features_simple (caps, gst_caps_features_new (
features, GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, NULL));
gst_caps_append (caps, tmp);
return caps;
}
GdkTexture *
gst_clapper_gl_context_handler_make_gl_texture (GstClapperGLContextHandler *self,
GstBuffer *buffer, GstVideoInfo *v_info)
{
GdkTexture *texture;
GstGLSyncMeta *sync_meta;
GstVideoFrame frame;
if (G_UNLIKELY (!gst_video_frame_map (&frame, v_info, buffer, GST_MAP_READ | GST_MAP_GL))) {
GST_ERROR_OBJECT (self, "Could not map input buffer for reading");
return NULL;
}
GST_OBJECT_LOCK (self);
/* Must have context active here for both sync meta
* and Gdk texture format auto-detection to work */
gdk_gl_context_make_current (self->gdk_context);
gst_gl_context_activate (self->wrapped_context, TRUE);
sync_meta = gst_buffer_get_gl_sync_meta (buffer);
/* Wait for all previous OpenGL commands to complete,
* before we start using the input texture */
if (sync_meta) {
gst_gl_sync_meta_set_sync_point (sync_meta, self->gst_context);
gst_gl_sync_meta_wait (sync_meta, self->wrapped_context);
}
texture = gdk_gl_texture_new (
self->gdk_context,
*(guint *) GST_VIDEO_FRAME_PLANE_DATA (&frame, 0),
GST_VIDEO_FRAME_WIDTH (&frame),
GST_VIDEO_FRAME_HEIGHT (&frame),
(GDestroyNotify) gst_buffer_unref,
gst_buffer_ref (buffer));
gst_gl_context_activate (self->wrapped_context, FALSE);
gdk_gl_context_clear_current ();
GST_OBJECT_UNLOCK (self);
gst_video_frame_unmap (&frame);
return texture;
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <gst/video/video.h>
#include <gst/gl/gl.h>
#include <gst/gl/gstglfuncs.h>
#include <gtk/gtk.h>
#include "gst/plugin/gstclappercontexthandler.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_GL_CONTEXT_HANDLER (gst_clapper_gl_context_handler_get_type())
G_DECLARE_FINAL_TYPE (GstClapperGLContextHandler, gst_clapper_gl_context_handler, GST, CLAPPER_GL_CONTEXT_HANDLER, GstClapperContextHandler)
#define GST_CLAPPER_GL_CONTEXT_HANDLER_CAST(obj) ((GstClapperGLContextHandler *)(obj))
#define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WAYLAND (GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND))
#define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11 (GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11))
#define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_GLX (GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11 && GST_GL_HAVE_PLATFORM_GLX)
#define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_EGL (GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11 && GST_GL_HAVE_PLATFORM_EGL)
#define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32 (GST_GL_HAVE_WINDOW_WIN32 && defined (GDK_WINDOWING_WIN32))
#define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_WGL (GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32 && GST_GL_HAVE_PLATFORM_WGL)
#define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_EGL (GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32 && GST_GL_HAVE_PLATFORM_EGL)
#define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_MACOS (GST_GL_HAVE_WINDOW_COCOA && defined (GDK_WINDOWING_MACOS) && GST_GL_HAVE_PLATFORM_CGL)
struct _GstClapperGLContextHandler
{
GstClapperContextHandler parent;
GdkGLContext *gdk_context;
GstGLDisplay *gst_display;
GstGLContext *wrapped_context;
GstGLContext *gst_context;
};
void gst_clapper_gl_context_handler_add_handler (GPtrArray *context_handlers);
GstCaps * gst_clapper_gl_context_handler_make_gdk_gl_caps (const gchar *features, gboolean only_2d);
GdkTexture * gst_clapper_gl_context_handler_make_gl_texture (GstClapperGLContextHandler *handler, GstBuffer *buffer, GstVideoInfo *v_info);
G_END_DECLS

View File

@@ -0,0 +1,140 @@
gst_clapper_gl_ch_dep = dependency('', required: false)
build_gl_ch = (
not get_option('glimporter').disabled()
or not get_option('gluploader').disabled()
)
gl_support_required = (
get_option('glimporter').enabled()
or get_option('gluploader').enabled()
)
# GStreamer OpenGL
gst_gl_dep = dependency('gstreamer-gl-1.0',
version: gst_req,
fallback: ['gst-plugins-base'],
required: false,
)
gst_gl_x11_dep = dependency('', required: false)
gst_gl_wayland_dep = dependency('', required: false)
gst_gl_egl_dep = dependency('', required: false)
gst_gl_apis = gst_gl_dep.get_variable('gl_apis').split()
gst_gl_winsys = gst_gl_dep.get_variable('gl_winsys').split()
gst_gl_platforms = gst_gl_dep.get_variable('gl_platforms').split()
message('GStreamer OpenGL window systems: @0@'.format(' '.join(gst_gl_winsys)))
message('GStreamer OpenGL platforms: @0@'.format(' '.join(gst_gl_platforms)))
message('GStreamer OpenGL apis: @0@'.format(' '.join(gst_gl_apis)))
foreach ws : ['x11', 'wayland', 'android', 'cocoa', 'eagl', 'win32', 'dispmanx', 'viv_fb']
set_variable('gst_gl_have_window_@0@'.format(ws), gst_gl_winsys.contains(ws))
endforeach
foreach p : ['glx', 'egl', 'cgl', 'eagl', 'wgl']
set_variable('gst_gl_have_platform_@0@'.format(p), gst_gl_platforms.contains(p))
endforeach
foreach api : ['gl', 'gles2']
set_variable('gst_gl_have_api_@0@'.format(api), gst_gl_apis.contains(api))
endforeach
gst_gl_proto_dep = dependency('gstreamer-gl-prototypes-1.0',
version: gst_req,
fallback: ['gst-plugins-base'],
required: true
)
if gst_gl_have_window_x11
gst_gl_x11_dep = dependency('gstreamer-gl-x11-1.0',
version: gst_req,
fallback: ['gst-plugins-base'],
required: true,
)
endif
if gst_gl_have_window_wayland
gst_gl_wayland_dep = dependency('gstreamer-gl-wayland-1.0',
version: gst_req,
fallback: ['gst-plugins-base'],
required: true,
)
endif
if gst_gl_have_platform_egl
gst_gl_egl_dep = dependency('gstreamer-gl-egl-1.0',
version: gst_req,
fallback: ['gst-plugins-base'],
required: true,
)
endif
gst_plugin_gl_ch_deps = [gst_clapper_sink_dep, gst_gl_dep, gst_gl_proto_dep]
have_gtk_gl_windowing = false
if gst_gl_have_window_x11 and (gst_gl_have_platform_egl or gst_gl_have_platform_glx)
gtk_x11_dep = dependency('gtk4-x11', required: false)
if gtk_x11_dep.found()
gst_plugin_gl_ch_deps += gtk_x11_dep
if gst_gl_have_platform_glx
gst_plugin_gl_ch_deps += gst_gl_x11_dep
endif
have_gtk_gl_windowing = true
endif
endif
if gst_gl_have_window_wayland and gst_gl_have_platform_egl
gtk_wayland_dep = dependency('gtk4-wayland', required: false)
if gtk_wayland_dep.found()
gst_plugin_gl_ch_deps += [gtk_wayland_dep, gst_gl_wayland_dep]
have_gtk_gl_windowing = true
endif
endif
if gst_gl_have_window_win32 and (gst_gl_have_platform_egl or gst_gl_have_platform_wgl)
gtk_win32_dep = dependency('gtk4-win32', required: false)
if gtk_win32_dep.found()
gst_plugin_gl_ch_deps += gtk_win32_dep
have_gtk_gl_windowing = true
endif
endif
if gst_gl_have_window_cocoa and gst_gl_have_platform_cgl
gtk_macos_dep = dependency('gtk4-macos', required: false)
if gtk_macos_dep.found()
gst_plugin_gl_ch_deps += gtk_macos_dep
have_gtk_gl_windowing = true
endif
endif
if not have_gtk_gl_windowing
if gl_support_required
error('GL-based importer was enabled, but support for current GL windowing is missing')
endif
build_gl_ch = false
endif
if gst_gl_have_platform_egl
gst_plugin_gl_ch_deps += gst_gl_egl_dep
endif
foreach dep : gst_plugin_gl_ch_deps
if not dep.found()
if gl_support_required
error('GL-based importer was enabled, but required dependencies were not found')
endif
build_gl_ch = false
endif
endforeach
if build_gl_ch
gst_clapper_gl_ch_dep = declare_dependency(
link_with: library('gstclapperglcontexthandler',
'gstclapperglcontexthandler.c',
c_args: gst_clapper_plugin_args,
include_directories: gst_plugin_conf_inc,
dependencies: gst_plugin_gl_ch_deps,
version: meson.project_version(),
install: true,
),
include_directories: gst_plugin_conf_inc,
dependencies: gst_plugin_gl_ch_deps,
)
endif

Some files were not shown because too many files have changed in this diff Show More