Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/streaming mode metadata hls rpro 6175 #232

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "red5pro-html-sdk-testbed",
"version": "5.6.0-RC1",
"version": "5.6.0-RC2",
"description": "Testbed examples for Red5 Pro HTML SDK",
"main": "src/js/index.js",
"repository": {
79 changes: 79 additions & 0 deletions src/page/test/subscribeMute/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Recognizing Broadcast Mute with Subscribers using Red5 Pro

This is an example of utilizing the failover mechanism of the Red5 Pro HTML SDK to select a subscriber based on browser support.

The default failover order is:

1. WebRTC
2. RTMP/Flash
3. HLS

When utilizing the auto-failover mechanism, the SDK - by default - will first test for WebRTC support and if missing will attempt to embed a subscriber SWF for the broadcast. If Flash is not supported in the browser, it will finally attempt to playback using HLS.

You can define the desired failover order from using `setPlaybackOrder`.

> For more detailed information on Configuring and Subscribing with the Red5 Pro SDK, please visit the [Red5 Pro Documentation](https://www.red5pro.com/docs/streaming/subscriber.html).

## Example Code
- **[index.html](index.html)**
- **[index.js](index.js)**

# How to Subscribe

Subscribing to a Red5 Pro stream requires a few components to function fully.

> The examples in this repo also utilize various es2015 shims and polyfills to support ease in such things as `Object.assign` and `Promises`. You can find the list of these utilities used in [https://github.com/red5pro/streaming-html5/tree/master/static/lib/es6](feature/update_docs_RPRO-5153).

# How to Recognize Mute of Media from a Broadcast

The publisher can "mute" and "unmute" their Camera and Micrphone during a broadcast - meaning they can toggle sending stream data from their Camera or Microphone during a live broadcast session.

Subscribers are notified of the "mute/unmute" event in the form of Metadata from the server. The metadata property is `streamingMode` and can be defined with the following values:

* `Video/Audio` - Both Camera and Microphone are being streamed.
* `Video` - Only the Camera is being streamed.
* `Audio` - Only the Microphone is being streamed.
* `Empty` - Neither Camera nor Microphone are being streamed.

In addition to the `streamingMode` being provided on Metadata Events, the HTML SDK also has a specific event you - as a developer - can listen for to react to changes to "mute/unmute" from a publisher:

* SDK Access: `red5prosdk.SubscriberEventTypes.STREAMING_MODE_CHANGE`
* String Value: `Subscribe.StreamingMode.Change`

## Example

After having established a subscriber, assign a handler to the `Subscriber.StreamignMode.Change` event:

```js
new red5prosdk.Red5ProSubscriber()
.init(config)
.then(function (subscriber) {
subscriber.on(red5prosdk.SubscriberEventTypes.STREAMING_MODE_CHANGE, handleStreamingModeChange);
return subscriber.subscribe()
})
```

The event handler:

```js
function handleStreamingModeChange (metadata) {
console.log('[Red5ProSubscriber] Broadcast Streaming Mode changed from (' + metadata.previousStreamingMode + ') to (' + metadata.streamingMode + ').');
switch (metadata.streamingMode) {
case 'Empty':
broadcastStatus.innerText = 'Publisher has turned off their Camera and Microphone.'
broadcastStatus.classList.remove('hidden');
break;
case 'Audio':
broadcastStatus.innerText = 'Publisher has turned off their Camera.'
broadcastStatus.classList.remove('hidden');
break;
case 'Video':
broadcastStatus.innerText = 'Publisher has turned off their Microphone.'
broadcastStatus.classList.remove('hidden');
break;
default:
broadcastStatus.classList.add('hidden');
break;
}
}
```
35 changes: 35 additions & 0 deletions src/page/test/subscribeMute/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!doctype html>
<html>
<head>
{{> meta title='Subscriber Test'}}
{{> header-scripts}}
{{> header-stylesheets}}
<style>
.broadcast-status-field {
padding: 0.8rem 0;
background-color: #bcd298;
border-top: 1px solid black;
}
</style>
</head>
<body>
<div id="app">
{{> version }}
{{> settings-link}}
{{> test-info testTitle='Subscriber Test'}}
{{> status-field-subscriber}}
{{> statistics-field}}
<p id="broadcast-status" class="centered broadcast-status-field hidden"></p>
<div class="centered">
<video id="red5pro-subscriber"
controls="controls" autoplay="autoplay" playsinline
class="red5pro-media red5pro-media-background"
width="640" height="480">
</video>
</div>
</div>
{{> body-scripts}}
{{> mobile-subscriber-util}}
<script src="index.js"></script>
</body>
</html>
248 changes: 248 additions & 0 deletions src/page/test/subscribeMute/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
(function(window, document, red5prosdk) {
'use strict';

var serverSettings = (function() {
var settings = sessionStorage.getItem('r5proServerSettings');
try {
return JSON.parse(settings);
}
catch (e) {
console.error('Could not read server settings from sessionstorage: ' + e.message);
}
return {};
})();

var configuration = (function () {
var conf = sessionStorage.getItem('r5proTestBed');
try {
return JSON.parse(conf);
}
catch (e) {
console.error('Could not read testbed configuration from sessionstorage: ' + e.message);
}
return {}
})();
red5prosdk.setLogLevel(configuration.verboseLogging ? red5prosdk.LOG_LEVELS.TRACE : red5prosdk.LOG_LEVELS.WARN);

var targetSubscriber;

var updateStatusFromEvent = window.red5proHandleSubscriberEvent; // defined in src/template/partial/status-field-subscriber.hbs
var instanceId = Math.floor(Math.random() * 0x10000).toString(16);
var streamTitle = document.getElementById('stream-title');
var statisticsField = document.getElementById('statistics-field');
var broadcastStatus = document.getElementById('broadcast-status');

var protocol = serverSettings.protocol;
var isSecure = protocol === 'https';

var bitrate = 0;
var packetsReceived = 0;
var frameWidth = 0;
var frameHeight = 0;
function updateStatistics (b, p, w, h) {
statisticsField.innerText = 'Bitrate: ' + Math.floor(b) + '. Packets Received: ' + p + '.' + ' Resolution: ' + w + ', ' + h + '.';
}

function onBitrateUpdate (b, p) {
bitrate = b;
packetsReceived = p;
updateStatistics(bitrate, packetsReceived, frameWidth, frameHeight);
}

function onResolutionUpdate (w, h) {
frameWidth = w;
frameHeight = h;
updateStatistics(bitrate, packetsReceived, frameWidth, frameHeight);
}

// Determines the ports and protocols based on being served over TLS.
function getSocketLocationFromProtocol () {
return !isSecure
? {protocol: 'ws', port: serverSettings.wsport}
: {protocol: 'wss', port: serverSettings.wssport};
}

// Base configuration to extend in providing specific tech failover configurations.
var defaultConfiguration = (function(useVideo, useAudio) {
var c = {
protocol: getSocketLocationFromProtocol().protocol,
port: getSocketLocationFromProtocol().port
};
if (!useVideo) {
c.videoEncoding = red5prosdk.PlaybackVideoEncoder.NONE;
}
if (!useAudio) {
c.audioEncoding = red5prosdk.PlaybackAudioEncoder.NONE;
}
return c;
})(configuration.useVideo, configuration.useAudio);

// Local lifecycle notifications.
function onSubscriberEvent (event) {
if (event.type !== 'Subscribe.Time.Update') {
console.log('[Red5ProSubscriber] ' + event.type + '.');
updateStatusFromEvent(event);
if (event.type === 'Subscribe.StreamingMode.Change') {
handleStreamingModeChange(event.data);
}
}
}
function onSubscribeFail (message) {
console.error('[Red5ProSubsriber] Subscribe Error :: ' + message);
}
function onSubscribeSuccess (subscriber) {
console.log('[Red5ProSubsriber] Subscribe Complete.');
if (window.exposeSubscriberGlobally) {
window.exposeSubscriberGlobally(subscriber);
}
if (subscriber.getType().toLowerCase() === 'rtc') {
try {
window.trackBitrate(subscriber.getPeerConnection(), onBitrateUpdate, onResolutionUpdate, true);
}
catch (e) {
//
}
}
}
function onUnsubscribeFail (message) {
console.error('[Red5ProSubsriber] Unsubscribe Error :: ' + message);
}
function onUnsubscribeSuccess () {
console.log('[Red5ProSubsriber] Unsubscribe Complete.');
}

function handleStreamingModeChange (metadata) {
console.log('[Red5ProSubscriber] Broadcast Streaming Mode changed from (' + metadata.previousStreamingMode + ') to (' + metadata.streamingMode + ').');
switch (metadata.streamingMode) {
case 'Empty':
broadcastStatus.innerText = 'Publisher has turned off their Camera and Microphone.'
broadcastStatus.classList.remove('hidden');
break;
case 'Audio':
broadcastStatus.innerText = 'Publisher has turned off their Camera.'
broadcastStatus.classList.remove('hidden');
break;
case 'Video':
broadcastStatus.innerText = 'Publisher has turned off their Microphone.'
broadcastStatus.classList.remove('hidden');
break;
default:
broadcastStatus.classList.add('hidden');
break;
}
}

function getAuthenticationParams () {
var auth = configuration.authentication;
return auth && auth.enabled
? {
connectionParams: {
username: auth.username,
password: auth.password
}
}
: {};
}

// Request to unsubscribe.
function unsubscribe () {
return new Promise(function(resolve, reject) {
var subscriber = targetSubscriber
subscriber.unsubscribe()
.then(function () {
targetSubscriber.off('*', onSubscriberEvent);
targetSubscriber = undefined;
onUnsubscribeSuccess();
resolve();
})
.catch(function (error) {
var jsonError = typeof error === 'string' ? error : JSON.stringify(error, null, 2);
onUnsubscribeFail(jsonError);
reject(error);
});
});
}

// Define tech spefific configurations for each failover item.
var config = Object.assign({},
configuration,
defaultConfiguration,
getAuthenticationParams());
var rtcConfig = Object.assign({}, config, {
protocol: getSocketLocationFromProtocol().protocol,
port: getSocketLocationFromProtocol().port,
subscriptionId: 'subscriber-' + instanceId,
streamName: config.stream1,
})
var rtmpConfig = Object.assign({}, config, {
protocol: 'rtmp',
port: serverSettings.rtmpport,
streamName: config.stream1,
width: config.cameraWidth,
height: config.cameraHeight,
backgroundColor: '#000000',
swf: '../../lib/red5pro/red5pro-subscriber.swf',
swfobjectURL: '../../lib/swfobject/swfobject.js',
productInstallURL: '../../lib/swfobject/playerProductInstall.swf'
})
var hlsConfig = Object.assign({}, config, {
protocol: protocol,
port: isSecure ? serverSettings.hlssport : serverSettings.hlsport,
streamName: config.stream1,
mimeType: 'application/x-mpegURL'
})

// Define failover order.
var subscribeOrder = config.subscriberFailoverOrder
.split(',').map(function (item) {
return item.trim();
});

// Override for providing ?view= query param.
if (window.query('view')) {
subscribeOrder = [window.query('view')];
}

// Request to initialization and start subscribing through failover support.
var subscriber = new red5prosdk.Red5ProSubscriber()
subscriber.setPlaybackOrder(subscribeOrder)
.init({
rtc: rtcConfig,
rtmp: rtmpConfig,
hls: hlsConfig
})
.then(function (subscriberImpl) {
streamTitle.innerText = configuration.stream1;
targetSubscriber = subscriberImpl
// Subscribe to events.
targetSubscriber.on('*', onSubscriberEvent);
return targetSubscriber.subscribe()
})
.then(function () {
onSubscribeSuccess(targetSubscriber);
})
.catch(function (error) {
var jsonError = typeof error === 'string' ? error : JSON.stringify(error, null, 2);
console.error('[Red5ProSubscriber] :: Error in subscribing - ' + jsonError);
onSubscribeFail(jsonError);
});

// Clean up.
var shuttingDown = false;
function shutdown() {
if (shuttingDown) return;
shuttingDown = true;
function clearRefs () {
if (targetSubscriber) {
targetSubscriber.off('*', onSubscriberEvent);
}
targetSubscriber = undefined;
}
unsubscribe().then(clearRefs).catch(clearRefs);
window.untrackBitrate();
}
window.addEventListener('pagehide', shutdown);
window.addEventListener('beforeunload', shutdown);

})(this, document, window.red5prosdk);

1 change: 1 addition & 0 deletions src/page/testbed-menu.html
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@ <h1 class="centered">Red5 Pro HTML Testbed</h1>
<li><a href="test/subscribeRoundTripAuth">Subscribe - RoundTrip Authentication</a></li>
<li><a href="test/subscribeRemoteCall">Subscribe - Remote Call</a></li>
<li><a href="test/subscribeReconnect">Subscribe - Reconnect</a></li>
<li><a href="test/subscribeMute">Subscribe - Mute</a></li>
<li><a href="test/subscribeRetryOnInvalidName">Subscribe - Retry On Connection (WebRTC)</a></li>
<li><a href="test/subscribeScreenShare">Subscribe - ScreenShare (WebRTC)</a></li>
<li><a href="test/subscribeTwoStreams">Subscribe - Two Streams</a></li>
1 change: 1 addition & 0 deletions static/lib/red5pro/SUBSCRIBER_README.md
Original file line number Diff line number Diff line change
@@ -491,6 +491,7 @@ The following events are common across all Subscriber implementations from the R
| PLAY_UNPUBLISH | 'Subscribe.Play.Unpublish' | Notification of when a live broadcast has stopped publishing. |
| CONNECTION_CLOSED | 'Subscribe.Connection.Closed' | Invoked when a close to the connection is detected. |
| ORIENTATION_CHANGE | 'Subscribe.Orientation.Change' | Invoked when an orientation change is detected in metadata. Mobile (iOS and Android) broadcasts are sent with an orientation. |
| STREAMING_MODE_CHANGE | 'Subscribe.StreamingMode.Change' | Invoked when the broadcast has "muted" either or both their video and audio tracks. |
| VOLUME_CHANGE | 'Subscribe.Volume.Change' | Invoked when a change to volume is detected during playback. _From 0 to 1._ |
| PLAYBACK_TIME_UPDATE | 'Subscribe.Time.Update' | Invoked when a change in playhead time is detected during playback. _In seconds._ |
| PLAYBACK_STATE_CHANGE | 'Subscribe.Playback.Change' | Invoked when a change in playback state has occured, such as when going from a `Playback.PAUSED` state to `Playback.PLAYING` state. |
Binary file modified static/lib/red5pro/red5pro-publisher.swf
Binary file not shown.
4 changes: 2 additions & 2 deletions static/lib/red5pro/red5pro-sdk.min.js

Large diffs are not rendered by default.

Binary file modified static/lib/red5pro/red5pro-subscriber.swf
Binary file not shown.