summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRay Strode <rstrode@redhat.com>2011-06-13 11:45:44 -0400
committerRay Strode <rstrode@redhat.com>2011-06-13 12:51:20 -0400
commitf258b4490beb89eacb1ca21fcca37bb5e135643e (patch)
tree1ce22a96022b48699320122a9f745ce1214d795b
initial import
This is just a couple of hours worth of hacking, to try to sketch out a kiosk app for streaming. It doesn't do much yet besides show a preview video.
-rw-r--r--default.css13
-rwxr-xr-xgst-icecast70
-rw-r--r--icecastClient.js87
-rw-r--r--init.js120
-rwxr-xr-xlive-meet-feed2
-rw-r--r--live-meet-feed.desktop4
-rw-r--r--panel.js67
-rw-r--r--play.js116
-rw-r--r--video.js39
9 files changed, 518 insertions, 0 deletions
diff --git a/default.css b/default.css
new file mode 100644
index 0000000..774f1b0
--- /dev/null
+++ b/default.css
@@ -0,0 +1,13 @@
+.panel-item {
+ background-color: #555555;
+}
+
+#panel {
+ background-color: #555555;
+}
+
+#stage-layout {
+ background-color: black;
+}
+
+
diff --git a/gst-icecast b/gst-icecast
new file mode 100755
index 0000000..8cc4d4a
--- /dev/null
+++ b/gst-icecast
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+output="meeting-`date +%Y.%m.%d-%H.%M.%S.ogg`"
+
+echo "Stream url is http://livefeed.lab.bos.redhat.com/stream-hi.ogg"
+echo "Stream url is http://livefeed.lab.bos.redhat.com/stream-lo.ogg"
+
+sudo gst-launch \
+ hdv1394src blocksize="4136" \
+ ! queue \
+ ! mpegtsdemux name=demux \
+ demux. \
+ ! queue \
+ ! mpeg2dec \
+ ! queue \
+ ! ffvideoscale \
+ ! video/x-raw-yuv,width=640,height=360,pixel-aspect-ratio=1/1 \
+ ! queue \
+ ! videorate \
+ ! video/x-raw-yuv,framerate=12/1 \
+ ! tee name=lovid \
+ ! queue \
+ ! theoraenc quality=32 bitrate=350 keyframe-force=64 \
+ ! tee name=preview \
+ ! queue \
+ ! himux. \
+ lovid. \
+ ! queue \
+ ! videorate \
+ ! video/x-raw-yuv,framerate=6/1 \
+ ! queue \
+ ! theoraenc quality=20 bitrate=130 keyframe-force=64 \
+ ! queue \
+ ! lomux. \
+ demux. \
+ ! queue \
+ ! mad \
+ ! tee name=loaud \
+ ! audioconvert \
+ ! audioresample \
+ ! audio/x-raw-float,channels=1,rate=48000 \
+ ! queue \
+ ! vorbisenc quality=.15 \
+ ! queue \
+ ! himux.\
+ loaud. \
+ ! audioconvert \
+ ! audioresample \
+ ! audio/x-raw-float,channels=1,rate=16000 \
+ ! queue \
+ ! vorbisenc quality=.1 \
+ ! queue \
+ ! lomux.\
+ oggmux name=himux \
+ ! tee name=save \
+ ! queue \
+ ! progressreport \
+ ! shout2send ip=an-icecast-server-here \
+ port=80 password=a-password-here mount=stream-hi.ogg \
+ oggmux name=lomux \
+ ! queue \
+ ! shout2send ip=an-icecast-server-here \
+ port=80 password=a-password-here mount=stream-lo.ogg \
+ save. \
+ ! queue \
+ ! filesink location=$output \
+ preview. \
+ ! queue \
+ ! theoradec \
+ ! xvimagesink sync="false"
diff --git a/icecastClient.js b/icecastClient.js
new file mode 100644
index 0000000..a9b7978
--- /dev/null
+++ b/icecastClient.js
@@ -0,0 +1,87 @@
+imports.gi.versions.GLib = '2.0';
+imports.gi.versions.GObject = '2.0';
+imports.gi.versions.Gio = '2.0';
+
+imports.gi.versions.Gdk = '3.0';
+imports.gi.versions.Gtk = '3.0';
+
+imports.gi.versions.Clutter = '1.0';
+
+imports.gi.versions.Gst = '0.10';
+imports.gi.versions.GstBase = '0.10';
+imports.gi.versions.GstVideo = '0.10';
+
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+
+const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
+const Gio = imports.gi.Gio;
+
+const Gdk = imports.gi.Gdk;
+const Gtk = imports.gi.Gtk;
+
+const Clutter = imports.gi.Clutter;
+const ClutterGst = imports.gi.ClutterGst;
+
+const Gst = imports.gi.Gst;
+const GstBase = imports.gi.GstBase;
+const GstApp = imports.gi.GstApp;
+const GstVideo = imports.gi.GstVideo;
+
+const KEYFRAME_INTERVAL = 64;
+
+function IcecastClient() {
+ this._init.apply(this, arguments);
+}
+
+IcecastClient.prototype = {
+ _init: function(feeds) {
+ this._feeds = feeds;
+ this._pipeline = Gst.Pipeline.new('stream');
+ this._bus = this._pipeline.get_bus();
+
+ this._bus.connect('message::eos',
+ Lang.bind(this, this._onEndOfStream));
+ this._bus.connect('message::error',
+ Lang.bind(this, this._onErrorMessage));
+
+ _setupPipeline();
+ },
+
+ _findInputSource: function() {
+ let cameraSource = Gst.ElementFactory.make('camerabin',
+ 'camera-source');
+
+ if (!cameraSource) {
+ return null;
+ }
+
+ cameraSource.video_capture_height = 0;
+ cameraSource.video_capture_width = 0;
+
+ return cameraSource;
+ },
+
+ _setupPipeline: function() {
+ this._inputSource = _findInputSource();
+
+ if (!this._inputSource)
+ throw new Error("could not find input source for stream");
+
+ this._pipeline.add(this._inputSource);
+ },
+
+ _onEndOfStream: function(bus, message) {
+ },
+
+ _onErrorMessage: function(bus, message) {
+ },
+
+ start: function() {
+ },
+
+ stop: function() {
+ }
+}
+
diff --git a/init.js b/init.js
new file mode 100644
index 0000000..d8dc5ae
--- /dev/null
+++ b/init.js
@@ -0,0 +1,120 @@
+imports.gi.versions.GLib = '2.0';
+imports.gi.versions.GObject = '2.0';
+imports.gi.versions.Gio = '2.0';
+
+imports.gi.versions.Gdk = '3.0';
+imports.gi.versions.Gtk = '3.0';
+
+imports.gi.versions.Clutter = '1.0';
+imports.gi.versions.Gst = '0.10';
+imports.gi.versions.GstBase = '0.10';
+imports.gi.versions.GstVideo = '0.10';
+
+const Mainloop = imports.mainloop;
+
+const IcecastClient = imports.icecastClient;
+const Panel = imports.panel;
+
+const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
+const Gio = imports.gi.Gio;
+
+const Gdk = imports.gi.Gdk;
+const Gtk = imports.gi.Gtk;
+
+const Clutter = imports.gi.Clutter;
+const Mx = imports.gi.Mx;
+
+const Gst = imports.gi.Gst;
+const ClutterGst = imports.gi.ClutterGst;
+
+let application = null;
+let style = null;
+let stage = null;
+let stageLayout = null;
+
+_init();
+
+function _initApplication() {
+ Gtk.init(null, null);
+
+ let settings = Gtk.Settings.get_default();
+ settings.gtk_application_prefer_dark_theme = true;
+
+ application = new Gtk.Application({ application_id: 'org.gnome.LiveMeetFeed' });
+}
+
+function _initGStreamer() {
+ Gst.init(null, null);
+ Gst.debug_set_active(true);
+ Gst.debug_set_default_threshold(Gst.DebugLevel.WARNING);
+ Gst.debug_set_colored(false);
+}
+
+function _initStyle() {
+ style = Mx.Style.get_default();
+ style.load_from_file('default.css');
+}
+
+function _initStage() {
+ Clutter.init(null, null);
+
+ stage = Clutter.Stage.get_default();
+ stage.set_size(512, 512);
+ stage.set_user_resizable(true);
+ stage.connect('delete-event',
+ function() {
+ Mainloop.quit('');
+ return true;
+ });
+ stage.set_title("Live Meet Feed");
+
+ stageLayout = new Mx.BoxLayout({ name: 'stage-layout',
+ orientation: Mx.Orientation.VERTICAL });
+ stage.add_actor(stageLayout);
+
+ stageLayout.add_constraint(new Clutter.BindConstraint({ source: stage,
+ coordinate: Clutter.BindCoordinate.SIZE }));
+ stageLayout.add_constraint(new Clutter.BindConstraint({ source: stage,
+ coordinate: Clutter.BindCoordinate.POSITION }));
+}
+
+function _init() {
+ _initApplication();
+ _initGStreamer();
+ _initStyle();
+ _initStage();
+
+ let pipeline = Gst.Pipeline.new('feed-pipeline');
+
+ let texture = new Clutter.Texture({ name: 'video-texture' });
+ stageLayout.add_actor(texture, -1);
+ stageLayout.child_set_expand(texture, true);
+ stageLayout.child_set_y_fill(texture, true);
+ stageLayout.child_set_y_align(texture, 0.0);
+ texture.show();
+
+ let source = Gst.ElementFactory.make('camerabin', 'camera-source');
+ pipeline.add(source);
+
+ let sink = new ClutterGst.VideoSink({ texture: texture });
+ source.viewfinder_sink = sink;
+
+ pipeline.set_state(Gst.State.PLAYING);
+
+ let panel = new Panel.Panel();
+ stageLayout.add_actor(panel.actor, -1);
+ stageLayout.child_set_expand(panel.actor, false);
+ stageLayout.child_set_x_align(panel.actor, 0.0);
+ stageLayout.child_set_x_fill(panel.actor, true);
+ stageLayout.child_set_y_align(panel.actor, 1.0);
+ stageLayout.child_set_y_fill(panel.actor, false);
+
+ panel.connect('toggle-fullscreen',
+ function () {
+ stage.set_fullscreen(!stage.get_fullscreen());
+ });
+
+ stage.show_all();
+ Mainloop.run('');
+}
diff --git a/live-meet-feed b/live-meet-feed
new file mode 100755
index 0000000..7df7b20
--- /dev/null
+++ b/live-meet-feed
@@ -0,0 +1,2 @@
+#!/bin/sh
+gjs -I $PWD init.js
diff --git a/live-meet-feed.desktop b/live-meet-feed.desktop
new file mode 100644
index 0000000..47d6676
--- /dev/null
+++ b/live-meet-feed.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Name=Live Meet Feed
+Exec=live-meet-feed
+Type=Application
diff --git a/panel.js b/panel.js
new file mode 100644
index 0000000..bfee077
--- /dev/null
+++ b/panel.js
@@ -0,0 +1,67 @@
+imports.gi.versions.GLib = '2.0';
+imports.gi.versions.GObject = '2.0';
+imports.gi.versions.Gio = '2.0';
+
+imports.gi.versions.Gdk = '3.0';
+imports.gi.versions.Gtk = '3.0';
+
+imports.gi.versions.Clutter = '1.0';
+
+imports.gi.versions.Gst = '0.10';
+imports.gi.versions.GstBase = '0.10';
+imports.gi.versions.GstVideo = '0.10';
+
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const Signals = imports.signals;
+
+const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
+const Gio = imports.gi.Gio;
+
+const Gdk = imports.gi.Gdk;
+const Gtk = imports.gi.Gtk;
+
+const Clutter = imports.gi.Clutter;
+const Mx = imports.gi.Mx;
+
+function Panel() {
+ this._init.apply(this, arguments);
+}
+
+Panel.prototype = {
+ _init: function() {
+ this.actor = new Mx.BoxLayout({ name: 'panel' });
+ this._broadcastButton = new Mx.Button({ style_class: 'panel-item' });
+ this._broadcastButton.set_icon_name('media-record-symbolic');
+ this._broadcastButton.set_icon_size(16);
+ this.actor.add_actor(this._broadcastButton, -1);
+ this.actor.child_set_x_align(this._broadcastButton, 0.0);
+
+ this._broadcastButton.connect('clicked',
+ Lang.bind(this, function() {
+ this.emit('toggle-broadcast');
+ }));
+
+ this._slider = new Mx.Slider({ style_class: 'panel-item',
+ disabled: true });
+ this.actor.add_actor(this._slider, -1);
+ this.actor.child_set_expand(this._slider, true);
+ this.actor.child_set_x_align(this._slider, 0.5);
+ this.actor.child_set_x_fill(this._slider, true);
+
+ this._fullscreenButton = new Mx.Button({ style_class: 'panel-item' });
+ this._fullscreenButton.set_icon_name('view-fullscreen-symbolic');
+ this._fullscreenButton.set_icon_size(16);
+ this.actor.add_actor(this._fullscreenButton, -1);
+ this.actor.child_set_x_align(this._fullscreenButton, 1.0);
+
+ this._fullscreenButton.connect('clicked',
+ Lang.bind(this, function() {
+ this.emit('toggle-fullscreen');
+ }));
+ this.actor.show_all();
+ },
+}
+Signals.addSignalMethods(Panel.prototype)
+
diff --git a/play.js b/play.js
new file mode 100644
index 0000000..cfe1bd9
--- /dev/null
+++ b/play.js
@@ -0,0 +1,116 @@
+imports.gi.versions.GLib = '2.0';
+imports.gi.versions.GObject = '2.0';
+imports.gi.versions.Gio = '2.0';
+
+imports.gi.versions.Gdk = '3.0';
+imports.gi.versions.Gtk = '3.0';
+
+imports.gi.versions.Clutter = '1.0';
+
+imports.gi.versions.Gst = '0.10';
+imports.gi.versions.GstBase = '0.10';
+imports.gi.versions.GstVideo = '0.10';
+
+const Mainloop = imports.mainloop;
+
+const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
+const Gio = imports.gi.Gio;
+
+const Gdk = imports.gi.Gdk;
+const Gtk = imports.gi.Gtk;
+
+const Clutter = imports.gi.Clutter;
+
+const Gst = imports.gi.Gst;
+const GstBase = imports.gi.GstBase;
+const GstApp = imports.gi.GstApp;
+const GstVideo = imports.gi.GstVideo;
+
+function _init() {
+ Gst.init(null, null);
+ Gst.debug_set_active(true);
+ Gst.debug_set_default_threshold(Gst.DebugLevel.WARNING);
+ Gst.debug_set_colored(false);
+
+ let pipeline = Gst.Pipeline.new('my-pipeline');
+
+ if (!pipeline) {
+ throw new Error('could not create pipeline');
+ }
+
+ let bus = pipeline.get_bus();
+ bus.connect('sync-message',
+ function(bus, message) {
+ print (message);
+ });
+ bus.connect('message::eos',
+ function() {
+ print('end of stream');
+ Mainloop.quit();
+ throw new Error('eos');
+ });
+ bus.connect('message::error',
+ function(bus, message) {
+ let [ error, debug ] = message.parse_error();
+ printerr('error: ' + error.message);
+ printerr('debug: ' + debug);
+ throw new Error('error');
+ });
+
+ let source = Gst.ElementFactory.make('filesrc', 'file-source');
+
+ if (!source) {
+ throw new Error('could not create element of type filesrc');
+ }
+ source.location = ARGV[0];
+ pipeline.add(source);
+
+ let demuxer = Gst.ElementFactory.make('oggdemux', 'ogg-demuxer');
+
+ if (!demuxer) {
+ throw new Error('could not create element of type oggdemux');
+ }
+ pipeline.add(demuxer);
+
+ let decoder = Gst.ElementFactory.make('vorbisdec', 'vorbis-decoder');
+
+ if (!decoder) {
+ throw new Error('could not create element of type vorbisdec');
+ }
+ pipeline.add(decoder);
+
+ let converter = Gst.ElementFactory.make('audioconvert', 'converter');
+
+ if (!converter) {
+ throw new Error('could not create element of type audioconvert');
+ }
+ pipeline.add(converter);
+
+ let sink = Gst.ElementFactory.make('autoaudiosink', 'audio-output');
+
+ if (!sink) {
+ throw new Error('could not create element of type autoaudiosink');
+ }
+ pipeline.add(sink);
+
+ source.link(demuxer);
+
+ demuxer.connect('pad-added',
+ function(element, pad) {
+ print('a new pad ' + pad.get_name() + ' was created');
+ let sinkPad = decoder.get_static_pad('sink');
+
+ pad.link(sinkPad);
+ });
+
+ decoder.link(converter);
+ converter.link(sink);
+
+ pipeline.set_state(Gst.State.PLAYING);
+ Mainloop.run('stream');
+
+ pipline.set_state(Gst.State.NULL);
+}
+
+_init();
diff --git a/video.js b/video.js
new file mode 100644
index 0000000..27106f1
--- /dev/null
+++ b/video.js
@@ -0,0 +1,39 @@
+imports.gi.versions.GLib = '2.0';
+imports.gi.versions.GObject = '2.0';
+imports.gi.versions.Gio = '2.0';
+
+imports.gi.versions.Gdk = '3.0';
+imports.gi.versions.Gtk = '3.0';
+
+imports.gi.versions.Clutter = '1.0';
+
+imports.gi.versions.Gst = '0.10';
+imports.gi.versions.GstBase = '0.10';
+imports.gi.versions.GstVideo = '0.10';
+
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+
+const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
+const Gio = imports.gi.Gio;
+
+const Gdk = imports.gi.Gdk;
+const Gtk = imports.gi.Gtk;
+
+const Clutter = imports.gi.Clutter;
+const Mx = imports.gi.Mx;
+
+function Video() {
+ this._init.apply(this, arguments);
+}
+
+Video.prototype = {
+ _init: function() {
+ this.actor = new Mx.BoxLayout({ style_class: 'video',
+ name: 'video',
+ reactive: true });
+ this.actor.show_all();
+ },
+}
+