diff options
author | Ray Strode <rstrode@redhat.com> | 2011-06-13 11:45:44 -0400 |
---|---|---|
committer | Ray Strode <rstrode@redhat.com> | 2011-06-13 12:51:20 -0400 |
commit | f258b4490beb89eacb1ca21fcca37bb5e135643e (patch) | |
tree | 1ce22a96022b48699320122a9f745ce1214d795b |
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.css | 13 | ||||
-rwxr-xr-x | gst-icecast | 70 | ||||
-rw-r--r-- | icecastClient.js | 87 | ||||
-rw-r--r-- | init.js | 120 | ||||
-rwxr-xr-x | live-meet-feed | 2 | ||||
-rw-r--r-- | live-meet-feed.desktop | 4 | ||||
-rw-r--r-- | panel.js | 67 | ||||
-rw-r--r-- | play.js | 116 | ||||
-rw-r--r-- | video.js | 39 |
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() { + } +} + @@ -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) + @@ -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(); + }, +} + |