Skip to content

Commit

Permalink
0.2.0
Browse files Browse the repository at this point in the history
Added instructions for installing, configuring, and building docker image to re-encode OctoPrint's built in mjpeg stream to YouTube Live.  Pulls stream url from OctoPrint settings and stream id from plugin settings to generate a docker command. Control stream from new button on bottom of YouTube Live plugin tab.
  • Loading branch information
jneilliii authored Jan 24, 2018
1 parent 27ee9a3 commit 0b32208
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 25 deletions.
24 changes: 6 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
# OctoPrint-YouTubeLive
**Overview:** Plugin that adds a tab to OctoPrint for viewing, starting, and stopping a YouTube Live stream.

**Overview:** A simple youtube live viewer tab for OctoPrint. Currently a work in progress and simple skeleton.

**Details:** This plugin in combination with the instructions found [here](https://blog.alexellis.io/live-stream-with-docker/) creates a pretty good streaming solution for OctoPrint. Currently tested with Octoprint running on Raspberry Pi Zero W and Google Chrome.

Would probably run better on a Pi 3 as the live stream does start to buffer running both OctoPrint and the stream from the same Pi Zero W. You could run your stream from any device, this plugin just creates the tab to allow you to look at the given channels live stream.
**Details:** Based on the work found [here](https://blog.alexellis.io/live-stream-with-docker/). Currently tested with OctoPrint running on a Raspberry Pi Zero W and on a Pi3.

<img src="https://raw.githubusercontent.com/jneilliii/Octoprint-YouTubeLive/master/tab_screenshot.jpg">

## Setup
## Requirements
Follow the instructions found [here](docker_instructions.md) to install and configure docker/mmjpeg for use with this plugin.

Once installed enter your YouTube's channel id which can be found on your [Advanced Account Settings](https://www.youtube.com/account_advanced). Enter your YouTube Channel ID into the YouTube Live settings within OctoPrint's settings dialog.
## Setup
Once installed enter your YouTube's channel id ([Advanced Account Settings](https://www.youtube.com/account_advanced)) and your YouTube stream id ([YouTube Live Dashboard](https://www.youtube.com/live_dashboard)) into the YouTube Live plugin settings.

<img src="https://raw.githubusercontent.com/jneilliii/Octoprint-YouTubeLive/master/settings_screenshot.jpg">

**Note:** If you use the same pi for both streaming and octoprint you will need to stop the webcamd service if using an OctoPi distribution. I ended up using the [SystemCommandEditor](https://github.com/Salandora/OctoPrint-SystemCommandEditor) plugin and added the following commands:

+ sudo service webcamd stop && docker run --privileged --name cam -d alexellis2/streaming:17-5-2017 *xxxx-xxxx-xxxx-xxxx*
+ docker stop cam && docker rm cam && sudo service webcamd start

manually:

sudo pip install https://github.com/jneilliii/OctoPrint-YouTubeLive/archive/master.zip

## Configuration

## TODO:
* [ ] Additional testing.
32 changes: 32 additions & 0 deletions docker_instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
**Install docker**

curl -sSL https://get.docker.com | sh
sudo usermod pi -aG docker
sudo reboot

**Pull Docker Image**

docker pull alexellis2/streaming:17-5-2017

**Clone Repository and Rebuild**

cd ~
git clone https://github.com/jneilliii/youtubelive --depth 1
cd youtubelive
docker build -t octoprint/youtubelive .

**Test**

Set up your stream on the [YouTube Live Dashboard](https://www.youtube.com/live_dashboard) and enter your stream id in the command below in place of xxxx-xxxx-xxxx-xxxx.

docker run --privileged --name YouTubeLive -ti octoprint/youtubelive:latest http://localhost:8080/?action=stream xxxx-xxxx-xxxx-xxxx

Stream should go live and re-encode the OctoPrint stream to YouTube. Once verified close ffmpeg and remove docker container.

ctrl+c
docker rm YouTubeLive

**OctoPrint Settings**

Enter your stream id used above in the OctoPrint-YouTubeLive plugin settings.

59 changes: 55 additions & 4 deletions octoprint_youtubelive/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,29 @@
from __future__ import absolute_import

import octoprint.plugin
from octoprint.server import user_permission
import docker

class youtubelive(octoprint.plugin.StartupPlugin,
octoprint.plugin.TemplatePlugin,
octoprint.plugin.AssetPlugin,
octoprint.plugin.SettingsPlugin):
octoprint.plugin.SettingsPlugin,
octoprint.plugin.SimpleApiPlugin):

def __init__(self):
self.client = docker.from_env()
self.container = None

##~~ StartupPlugin
def on_after_startup(self):
self._logger.info("OctoPrint-YouTubeLive loaded!")
self._logger.info("OctoPrint-YouTubeLive loaded! Checking stream status.")
try:
self.container = self.client.containers.get('YouTubeLive')
self._logger.info("%s is streaming " % self.container.name)
self._plugin_manager.send_plugin_message(self._identifier, dict(status=True,streaming=True))
except Exception, e:
self._logger.error(str(e))
self._plugin_manager.send_plugin_message(self._identifier, dict(status=True,streaming=False))

##~~ TemplatePlugin
def get_template_configs(self):
Expand All @@ -22,10 +36,47 @@ def get_assets(self):
js=["js/youtubelive.js"],
css=["css/youtubelive.css"]
)

##~~ SettingsPlugin
def get_settings_defaults(self):
return dict(channel_id="")
return dict(channel_id="",stream_id="",streaming=False)

##~~ SimpleApiPlugin mixin

def get_api_commands(self):
return dict(startStream=[],stopStream=[],checkStream=[])

def on_api_command(self, command, data):
if not user_permission.can():
from flask import make_response
return make_response("Insufficient rights", 403)

if command == 'startStream':
self._logger.info("Start stream command received for stream: %s" % self._settings.get(["stream_id"]))
if not self.container:
try:
self.container = self.client.containers.run("octoprint/youtubelive:latest",command=[self._settings.global_get(["webcam","stream"]),"pbea-b3pr-8513-40mh"],detach=True,privileged=True,name="YouTubeLive",auto_remove=True)
self._plugin_manager.send_plugin_message(self._identifier, dict(status=True,streaming=True))
except Exception, e:
self._plugin_manager.send_plugin_message(self._identifier, dict(error=str(e),status=True,streaming=False))
return
if command == 'stopStream':
self._logger.info("Stop stream command received.")
if self.container:
try:
self.container.stop()
self.container = None
self._plugin_manager.send_plugin_message(self._identifier, dict(status=True,streaming=False))
except Exception, e:
self._plugin_manager.send_plugin_message(self._identifier, dict(error=str(e),status=True,streaming=False))
else:
self._plugin_manager.send_plugin_message(self._identifier, dict(status=True,streaming=False))
if command == 'checkStream':
self._logger.info("Checking stream status.")
if self.container:
self._plugin_manager.send_plugin_message(self._identifier, dict(status=True,streaming=True))
else:
self._plugin_manager.send_plugin_message(self._identifier, dict(status=True,streaming=False))

##~~ Softwareupdate hook
def get_update_information(self):
Expand Down
94 changes: 93 additions & 1 deletion octoprint_youtubelive/static/js/youtubelive.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,109 @@ $(function () {

self.settingsViewModel = parameters[0];
self.channel_id = ko.observable();
self.stream_id = ko.observable();
self.streaming = ko.observable();
self.processing = ko.observable(false);
self.icon = ko.pureComputed(function() {
var icons = [];
if (self.streaming() && !self.processing()) {
icons.push('icon-stop');
}

if (!self.streaming() && !self.processing()){
icons.push('icon-play');
}

if (self.processing()) {
icons.push('icon-spin icon-spinner');
}

return icons.join(' ');
});
self.btnclass = ko.pureComputed(function() {
return self.streaming() ? 'btn-primary' : 'btn-danger';
});


// This will get called before the youtubeliveViewModel gets bound to the DOM, but after its depedencies have
// already been initialized. It is especially guaranteed that this method gets called _after_ the settings
// have been retrieved from the OctoPrint backend and thus the SettingsViewModel been properly populated.
self.onBefireBinding = function () {
self.channel_id(self.settingsViewModel.settings.plugins.youtubelive.channel_id());
self.stream_id(self.settingsViewModel.settings.plugins.youtubelive.stream_id());
};

self.onEventSettingsUpdated = function (payload) {
self.channel_id = self.settingsViewModel.settings.plugins.youtubelive.channel_id();
self.channel_id(self.settingsViewModel.settings.plugins.youtubelive.channel_id());
self.stream_id(self.settingsViewModel.settings.plugins.youtubelive.stream_id());
};

self.onAfterBinding = function() {
$.ajax({
url: API_BASEURL + "plugin/youtubelive",
type: "POST",
dataType: "json",
data: JSON.stringify({
command: "checkStream"
}),
contentType: "application/json; charset=UTF-8"
})
}

self.onDataUpdaterPluginMessage = function(plugin, data) {
if (plugin != "youtubelive") {
return;
}

if(data.error) {
new PNotify({
title: 'YouTube Live Error',
text: data.error,
type: 'error',
hide: false,
buttons: {
closer: true,
sticker: false
}
});
}

if(data.status) {
if(data.streaming == true) {
self.streaming(true);
} else {
self.streaming(false);
}

}

self.processing(false);
};

self.toggleStream = function() {
self.processing(true);
if (self.streaming()) {
$.ajax({
url: API_BASEURL + "plugin/youtubelive",
type: "POST",
dataType: "json",
data: JSON.stringify({
command: "stopStream"
}),
contentType: "application/json; charset=UTF-8"
})
} else {
$.ajax({
url: API_BASEURL + "plugin/youtubelive",
type: "POST",
dataType: "json",
data: JSON.stringify({
command: "startStream"
}),
contentType: "application/json; charset=UTF-8"
})
}
}
}

// This is how our plugin registers itself with the application, by adding some configuration information to
Expand Down
6 changes: 6 additions & 0 deletions octoprint_youtubelive/templates/youtubelive_settings.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@
<input type="text" class="input-block-level" data-bind="value: settings.plugins.youtubelive.channel_id" placeholder="Enter your personal channel id here">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('YouTube Stream ID') }}</label>
<div class="controls">
<input type="text" class="input-block-level" data-bind="value: settings.plugins.youtubelive.stream_id" placeholder="Enter your personal live stream id here">
</div>
</div>
</form>
3 changes: 3 additions & 0 deletions octoprint_youtubelive/templates/youtubelive_tab.jinja2
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<div id="youtubelive_wrapper">
<iframe data-bind="attr: {src: 'https://www.youtube.com/embed/live_stream?channel=' + settingsViewModel.settings.plugins.youtubelive.channel_id()}" frameborder="0" allowfullscreen></iframe>
</div>
<div class="row-fluid btn-group" style="text-align: center;padding-top: 20px;">
<button class="btn" data-bind="click: toggleStream, css: btnclass, disable:processing"><i data-bind="css: icon"></i><span data-bind="text: streaming() ? ' Stop' : ' Go Live'"></span></button>
</div>
Binary file modified settings_screenshot.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
plugin_name = "YouTube Live"

# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
plugin_version = "0.1.0"
plugin_version = "0.2.0"

# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
# module
Expand All @@ -33,7 +33,7 @@
plugin_license = "AGPLv3"

# Any additional requirements besides OctoPrint should be listed here
plugin_requires = []
plugin_requires = ["docker==2.7.0"]

### --------------------------------------------------------------------------------------------------------------------
### More advanced options that you usually shouldn't have to touch follow after this point
Expand Down

0 comments on commit 0b32208

Please sign in to comment.