streamium
Version:
Decentralized trustless video streaming using bitcoin payment channels.
409 lines (356 loc) • 11.8 kB
JavaScript
'use strict';
angular.module('streamium.provider.controller', ['ngRoute'])
.config(['$routeProvider',
function($routeProvider) {
var create = {
templateUrl: '/app/provider/create.html',
controller: 'CreateStreamCtrl'
};
$routeProvider.when('/', create);
$routeProvider.when('/t', create);
var screen = {
templateUrl: '/app/provider/empty.html',
controller: 'ScreenCtrl'
};
$routeProvider.when('/screen', screen);
$routeProvider.when('/t/screen', screen);
var staticServe = {
templateUrl: '/app/provider/empty.html',
controller: 'StaticServeCtrl'
};
$routeProvider.when('/static', staticServe);
$routeProvider.when('/t/static', staticServe);
var broadcast = {
templateUrl: '/app/provider/stream.html',
controller: 'BroadcastStreamCtrl'
};
$routeProvider.when('/b/:streamId', broadcast);
$routeProvider.when('/t/b/:streamId', broadcast);
var cashout = {
templateUrl: '/app/provider/cashout.html',
controller: 'CashoutStreamCtrl'
};
$routeProvider.when('/b/:streamId/cashout', cashout);
$routeProvider.when('/t/b/:streamId/cashout', cashout);
var tutorial = {
address: {
templateUrl: '/app/provider/tutorial-address.html',
controller: 'CashoutStreamCtrl'
}
};
$routeProvider.when('/tutorial-address', tutorial.address);
}
])
.controller('ScreenCtrl', function($rootScope, $location) {
$rootScope.castType = 'screen';
$location.path(config.appPrefix);
})
.controller('StaticServeCtrl', function($rootScope, $location) {
$rootScope.castType = 'static';
$location.path(config.appPrefix);
})
.controller('CreateStreamCtrl', function($rootScope, $scope, $location, Rates, StreamiumProvider, bitcore, Insight, Stats) {
$scope.stream = {};
if (!$rootScope.castType) {
$rootScope.castType = 'webcam';
}
jQueryBackup('[data-toggle="tooltip"]').tooltip();
var storedStream = JSON.parse(localStorage.getItem('providerInfo') || '{}');
$scope.stream.name = storedStream.name || (config.DEBUG ? config.defaults.providerStream : '');
$scope.stream.address = storedStream.address || (config.DEBUG ? config.defaults.providerAddress : '');
$scope.stream.rate = storedStream.rate || config.defaults.rateUSD;
$scope.stream.error = null;
$scope.stream.loading = false;
$scope.config = config;
$scope.otherNetwork = config.otherNetwork;
$scope.linkToOther = config.linkToOther;
if (!DetectRTC.isWebRTCSupported) {
$scope.nowebrtc = true;
}
$scope.normalizeName = function() {
var name = $scope.stream.name || '';
name = name.trim().toLowerCase().replace(/ |\\/g, '-');
$scope.stream.name = name;
};
$scope.usdHourToBtcSec = function(usdHour) {
if (!Rates.rate) {
return 0;
}
var usdSecond = usdHour / 3600;
var btcSecond = bitcore.Unit.fromFiat(usdSecond, Rates.rate).toBTC();
return btcSecond ? btcSecond : 0;
};
$scope.usdHourToBtcMin = function(usdHour) {
if (!Rates.rate) {
return 0;
}
var usdSecond = usdHour / 60;
var btcSecond = bitcore.Unit.fromFiat(usdSecond, Rates.rate).toBTC();
return btcSecond ? btcSecond : 0;
};
Stats.homepage();
$scope.switchNetwork = function() {
window.location = window.location.origin + config.linkToOther;
};
jQueryBackup(function() {
retrievePendingTxs(Insight);
});
document.querySelector('input[type=file]').onchange = function () {
StreamiumProvider.fileToSend = this.files[0];
};
$scope.submit = function() {
if (!$scope.form.$valid) {
return;
}
var priceRate = $scope.usdHourToBtcMin($scope.stream.rate);
if (priceRate <= 0) {
$scope.stream.error = 'Price rate must be a positive number';
return;
}
$scope.stream.loading = true;
Stats.provider.createdStream(priceRate, $scope.stream.name, $scope.stream.address);
StreamiumProvider.init(
$scope.stream.name,
$scope.stream.address,
priceRate,
$rootScope.castType === 'static',
$scope.filename,
function onCreate(err) {
$scope.stream.loading = false;
if (err === StreamiumProvider.ERROR.UNREACHABLE) {
$scope.stream.error = 'Sorry, we\'re unable to start the stream right now. Mind trying again later?';
} else if (err === StreamiumProvider.ERROR.IDISTAKEN) {
$scope.stream.error = 'Looks like that channel name is already taken. Mind trying a different one?';
} else if (err === null) {
localStorage.setItem('providerInfo', JSON.stringify({
name: $scope.stream.name,
address: $scope.stream.address,
rate: $scope.stream.rate
}));
$location.path(config.appPrefix + '/b/' + $scope.stream.name);
} else {
console.log(err);
}
$scope.$apply();
}
);
};
})
.controller('BroadcastStreamCtrl', function($rootScope, $scope, $location, $routeParams, video, StreamiumProvider, Stats, Streamer) {
$scope.PROVIDER_COLOR = config.PROVIDER_COLOR;
$scope.name = $routeParams.streamId;
$scope.requiresApproval = true;
if ($rootScope.castType === 'static') {
$scope.requiresApproval = false;
$scope.isStatic = StreamiumProvider.isStatic;
$scope.filename = StreamiumProvider.filename;
$scope.client = StreamiumProvider;
$scope.filming = true;
video.setPeer(StreamiumProvider.peer);
}
$scope.isChrome = !!navigator.webkitGetUserMedia;
$scope.isFirefox = !!navigator.mozGetUserMedia;
$scope.castType = $rootScope.castType;
$scope.peers = {};
$scope.message = '';
$scope.messages = [];
var started = new Date();
$scope.switchScreen = function(ev) {
Stats.provider.castingScreen($scope.name, StreamiumProvider.address.toString());
$rootScope.switched = true;
$scope.castType = $rootScope.castType = ($rootScope.castType === 'screen' ? 'webcam' : 'screen');
$scope.requiresApproval = true;
video.finish();
startCamera();
return false;
};
StreamiumProvider.on('broadcast:start', function(peer) {
console.log('Start broadcast for ' + peer);
$scope.peers[peer] = peer;
Stats.provider.clientJoined({
delayToJoin: (new Date().getTime() - started.getTime()) / 1000
});
if ($rootScope.castType === 'static') {
video.streamer = new Streamer();
video.streamer.push = function(data) {
StreamiumProvider.pushVideo(peer, data);
};
video.streamer.stream(StreamiumProvider.fileToSend);
} else {
video.broadcast(peer, function(err) {
if (err) throw err;
$scope.broadcasting = true;
$scope.$apply();
});
}
});
StreamiumProvider.on('broadcast:end', function(peer) {
if ($scope.peers[peer]) {
delete $scope.peers[peer];
video.end(peer);
}
$scope.$apply();
});
StreamiumProvider.on('chatroom:message', function(data) {
Stats.provider.chatMessage();
$scope.messages.push({
color: data.color,
text: data.message
});
if (data.color) $scope.$apply(); // Only for clients
});
StreamiumProvider.on('balanceUpdated', function() {
$scope.$apply();
});
$scope.end = function() {
StreamiumProvider.endAllBroadcasts();
$location.path(config.appPrefix + '/b/' + $routeParams.streamId + '/cashout');
};
$scope.chat = function() {
StreamiumProvider.sendMessage($scope.message);
$scope.message = '';
};
var startCamera = function() {
if ($rootScope.castType !== 'static') {
$scope.client = StreamiumProvider;
$scope.filming = true;
video.setPeer(StreamiumProvider.peer);
video.camera($rootScope.castType, function(err, stream) {
if (err) {
console.log('error starting video:', err);
return;
}
$scope.requiresApproval = false;
$scope.videoSrc = URL.createObjectURL(stream);
$scope.$digest();
});
}
};
if (!StreamiumProvider.streamId && !config.DEBUG) {
$location.path(config.appPrefix + '/');
return;
} else {
startCamera();
}
window.addEventListener('beforeunload', dontClose);
})
.controller('CashoutStreamCtrl', function($rootScope, StreamiumProvider, $location, Duration, $scope, bitcore, Stats) {
window.removeEventListener('beforeunload', dontClose);
$scope.client = StreamiumProvider;
$scope.totalMoney = 0;
$scope.clients = [];
for (var i in $scope.client.mapClientIdToProvider) {
var amount = $scope.client.mapClientIdToProvider[i].currentAmount;
var time = Duration.for(StreamiumProvider.rate, amount);
$scope.totalMoney += amount;
if (amount > 0) {
var txId = $scope.client.mapClientIdToProvider[i].paymentTx.id;
$scope.clients.push({
amount: amount,
timeSpent: time / 1000,
transactionName: txId.substr(0, 4) + '...' + txId.substr(60, 64),
transactionUrl: 'https://'
+ (bitcore.Networks.defaultNetwork.name === 'testnet' ? 'test-' : '')
+ 'insight.bitpay.com/tx/' + txId
});
}
}
Stats.provider.endedStream({
name: StreamiumProvider.name,
totalEarned: $scope.totalMoney,
totalTime: Duration.for(StreamiumProvider.rate, $scope.totalMoney) / 1000,
rate: StreamiumProvider.rate,
maxActive: StreamiumProvider.clientMaxActive,
castType: $rootScope.castType,
totalClients: StreamiumProvider.clientConnections.length
});
})
.directive('twitter',
function($timeout) {
return {
link: function(scope, element, attr) {
$timeout(function() {
if (twttr && twttr.widgets) {
twttr.widgets.createShareButton(
attr.url,
element[0],
function(el) {}, {
count: 'none',
text: attr.text
}
);
}
});
}
}
}
);
function retrievePendingTxs(Insight) {
var $ = jQueryBackup;
var broadcast = [];
var messages = [];
var i, txraw, parts, name, time, peerid;
for (i in localStorage) {
parts = i.split('_');
if (parts.length !== 3) {
continue;
}
txraw = localStorage.getItem(i);
if (i.indexOf('payment_') === 0) {
broadcast.push({
key: i,
name: parts[1],
tx: txraw,
peerid: parts[2]
});
}
if (i.indexOf('refund') === 0) {
name = parts[1];
time = parts[2];
if (time < new Date().getTime() / 1000) {
broadcast.push({
key: i,
name: parts[1],
tx: txraw,
time: parts[2]
});
} else {
messages.push(parts[1]);
}
}
}
/*
if (messages.length === 1) {
var msg = 'You have locked funds from the stream "' + messages[0] + '". Come back tomorrow to try to claim them!';
$('.top-right').notify({ message: msg}).show();
} else if (messages.length > 1) {
var msg = 'You have locked funds from the streams "' +
messages.join('", "') + '". Come back tomorrow to try to claim them!';
$('.top-right').notify({ message: msg}).show();
}
*/
broadcast.map(function(tx) {
Insight.broadcast(tx.tx, function(err, txid) {
if (err) {
if (tx.time) {
localStorage.removeItem(tx.key);
}
console.log('Could not broadcast transaction', broadcast[i]);
return;
}
var msg = 'Unclaimed funds from the stream "' + tx.name + '" were just sent to you. Check your wallet!';
$('.top-right').notify({
message: msg
}).show();
localStorage.removeItem(tx.key);
});
});
}
var dontClose = function(e) {
var e = e || window.event;
var question = 'This will end the broadcast';
if (e) {
e.returnValue = question;
}
return question;
};