--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/coffee/calls.coffee Mon Mar 28 02:28:08 2016 +0800
+class Tram.Call extends Backbone.Model
+ @on('change:local/stream', @updateLocalStreamURL)
+ @on('change:remote/stream', @updateRemoteStreamURL)
+ updateStreamURL: (sattr, uattr) ->
+ URL.revokeObjectURL(@get(uattr))
+ @set(uattr, URL.createObjectURL(stream))
+ updateLocalStreamURL: ->
+ @updateStreamURL('local/stream', 'local/stream/url')
+ updateRemoteStreamURL: ->
+ @updateStreamURL('remote/stream', 'remote/stream/url')
+class Tram.Calls extends Backbone.Collection
+class Tram.CallView extends Backbone.View
+ className: 'video-block'
+ template: $('#video-block-template').html()
+ 'click [data-mute-cam]': -> @model.set('local/video/muted', true)
+ 'click [data-unmute-cam]': -> @model.set('local/video/muted', false)
+ 'click [data-mute-mic]': -> @model.set('local/audio/muted', true)
+ 'click [data-unmute-mic]': -> @model.set('local/audio/muted', false)
+ 'click [data-mute-audio]': -> @model.set('remote/audio/muted', true)
+ 'click [data-unmute-audio]': -> @model.set('remote/audio/muted', false)
+ @$local = @$('video.local')
+ @$remote = @$('video.remote')
+ @listenTo(@model, 'change:local/stream/url', @updateLocal)
+ @listenTo(@model, 'change:remote/stream/url', @updateRemote)
+ @listenTo(@model, 'change:local/video/muted', @muteCam)
+ @listenTo(@model, 'change:local/audio/muted', @muteMic)
+ @listenTo(@model, 'change:remote/audio/muted', @muteAudio)
+ if @model.has('local/stream/url')
+ @updateVideo(@$local, @model.get('local/stream/url'))
+ if @model.has('remote/stream/url')
+ @updateVideo(@$remote, @model.get('remote/stream/url'))
+ updateVideo: ($video, url) ->
+ removeVideo: ($video) ->
+ @removeAttribute('src')
+ enableTracks: (tracks, state) ->
+ muted = @model.get('local/video/muted')
+ @enableTracks(@model.get('local/stream')?.getVideoTracks?(), not muted)
+ muted = @model.get('local/audio/muted')
+ @enableTracks(@model.get('local/stream')?.getAudioTracks?(), not muted)
+ muted = @model.get('remote/audio/muted')
+ @enableTracks(@model.get('remote/stream')?.getAudioTracks?(), not muted)
+ @rivet = rivets.bind(@el, model: @model, view: this)
+class Tram.CallsApp extends Backbone.View
+ @listenTo(@collection, 'add', @onAdd)
+ @listenTo(@collection, 'remove', @onRemove)
+ model.view = new Tram.CallView(model: model)
+ @$el.append model.view.render().el
--- a/index.html Mon Mar 28 01:39:51 2016 +0800
+++ b/index.html Mon Mar 28 02:28:08 2016 +0800
+ <script type="text/template" id="video-block-template">
+ <video class="remote" rv-src="model:remote/stream/url" autoplay></video>
+ <video class="local" rv-src="model:local/stream/url" autoplay muted></video>
+ <div class="buttons uk-text-center">
+ <button type="button" class="uk-button uk-button-success autofade" data-mute-cam
+ rv-if="model:local/stream" rv-hide="model:local/video/muted">
+ <i class="uk-icon-eye"></i>
+ <button type="button" class="uk-button uk-button-danger" data-unmute-cam
+ rv-if="model:local/stream" rv-show="model:local/video/muted">
+ <i class="uk-icon-eye-slash"></i>
+ <button type="button" class="uk-button uk-button-success autofade" data-mute-mic
+ rv-if="model:local/stream" rv-hide="model:local/audio/muted">
+ <i class="uk-icon-microphone"></i>
+ <button type="button" class="uk-button uk-button-danger" data-unmute-mic
+ rv-if="model:local/stream" rv-show="model:local/audio/muted">
+ <i class="uk-icon-microphone-slash"></i>
+ <button type="button" class="uk-button uk-button-success autofade" data-mute-audio
+ rv-if="model:remote/stream" rv-hide="model:remote/audio/muted">
+ <i class="uk-icon-volume-up"></i>
+ <button type="button" class="uk-button uk-button-danger" data-unmute-audio
+ rv-if="model:remote/stream" rv-show="model:remote/audio/muted">
+ <i class="uk-icon-volume-off"></i>
<script type="text/template" id="avatar-template">
<img class="avatar" rv-if="model:d/avatar" rv-src="model:d/avatar">
<div class="avatar" rv-unless="model:d/avatar" rv-text="model:d/handle | first-letter" rv-style="model:bjid | dumb-hash | fn view.getColors"></div>
<script src='/js/xmpp.js'></script>
<script src='/js/contacts.js'></script>
<script src='/js/messages.js'></script>
+ <script src='/js/calls.js'></script>
<script src='/js/favicon.js'></script>
<script src='/config.js'></script>
<script src='/js/index.js'></script>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/js/calls.js Mon Mar 28 02:28:08 2016 +0800
+// Generated by CoffeeScript 1.10.0
+ var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+ Tram.Call = (function(superClass) {
+ extend(Call, superClass);
+ return Call.__super__.constructor.apply(this, arguments);
+ Call.prototype.idAttribute = 'jid';
+ Call.prototype.initialize = function() {
+ this.on('change:local/stream', this.updateLocalStreamURL);
+ return this.on('change:remote/stream', this.updateRemoteStreamURL);
+ Call.prototype.updateStreamURL = function(sattr, uattr) {
+ URL.revokeObjectURL(this.get(uattr));
+ stream = this.get(sattr);
+ return this.set(uattr, URL.createObjectURL(stream));
+ return this.unset(uattr);
+ Call.prototype.updateLocalStreamURL = function() {
+ return this.updateStreamURL('local/stream', 'local/stream/url');
+ Call.prototype.updateRemoteStreamURL = function() {
+ return this.updateStreamURL('remote/stream', 'remote/stream/url');
+ Tram.Calls = (function(superClass) {
+ extend(Calls, superClass);
+ return Calls.__super__.constructor.apply(this, arguments);
+ Calls.prototype.model = Tram.Call;
+ })(Backbone.Collection);
+ Tram.CallView = (function(superClass) {
+ extend(CallView, superClass);
+ return CallView.__super__.constructor.apply(this, arguments);
+ CallView.prototype.tagName = 'div';
+ CallView.prototype.className = 'video-block';
+ CallView.prototype.template = $('#video-block-template').html();
+ CallView.prototype.events = {
+ 'click [data-mute-cam]': function() {
+ return this.model.set('local/video/muted', true);
+ 'click [data-unmute-cam]': function() {
+ return this.model.set('local/video/muted', false);
+ 'click [data-mute-mic]': function() {
+ return this.model.set('local/audio/muted', true);
+ 'click [data-unmute-mic]': function() {
+ return this.model.set('local/audio/muted', false);
+ 'click [data-mute-audio]': function() {
+ return this.model.set('remote/audio/muted', true);
+ 'click [data-unmute-audio]': function() {
+ return this.model.set('remote/audio/muted', false);
+ CallView.prototype.initialize = function() {
+ this.$el.html(this.template);
+ this.$local = this.$('video.local');
+ this.$remote = this.$('video.remote');
+ CallView.prototype.bind = function() {
+ this.listenTo(this.model, 'change:local/stream/url', this.updateLocal);
+ this.listenTo(this.model, 'change:remote/stream/url', this.updateRemote);
+ this.listenTo(this.model, 'change:local/video/muted', this.muteCam);
+ this.listenTo(this.model, 'change:local/audio/muted', this.muteMic);
+ return this.listenTo(this.model, 'change:remote/audio/muted', this.muteAudio);
+ CallView.prototype.updateLocal = function() {
+ if (this.model.has('local/stream/url')) {
+ return this.updateVideo(this.$local, this.model.get('local/stream/url'));
+ return this.removeVideo(this.$local);
+ CallView.prototype.updateRemote = function() {
+ if (this.model.has('remote/stream/url')) {
+ return this.updateVideo(this.$remote, this.model.get('remote/stream/url'));
+ return this.removeVideo(this.$remote);
+ CallView.prototype.updateVideo = function($video, url) {
+ return $video.each(function() {
+ CallView.prototype.removeVideo = function($video) {
+ return $video.each(function() {
+ return this.removeAttribute('src');
+ CallView.prototype.enableTracks = function(tracks, state) {
+ var i, len, results, track;
+ for (i = 0, len = tracks.length; i < len; i++) {
+ results.push(track.enabled = state);
+ CallView.prototype.muteCam = function() {
+ muted = this.model.get('local/video/muted');
+ return this.enableTracks((ref = this.model.get('local/stream')) != null ? typeof ref.getVideoTracks === "function" ? ref.getVideoTracks() : void 0 : void 0, !muted);
+ CallView.prototype.muteMic = function() {
+ muted = this.model.get('local/audio/muted');
+ return this.enableTracks((ref = this.model.get('local/stream')) != null ? typeof ref.getAudioTracks === "function" ? ref.getAudioTracks() : void 0 : void 0, !muted);
+ CallView.prototype.muteAudio = function() {
+ muted = this.model.get('remote/audio/muted');
+ return this.enableTracks((ref = this.model.get('remote/stream')) != null ? typeof ref.getAudioTracks === "function" ? ref.getAudioTracks() : void 0 : void 0, !muted);
+ CallView.prototype.render = function() {
+ this.rivet = rivets.bind(this.el, {
+ CallView.prototype.remove = function() {
+ return CallView.__super__.remove.apply(this, arguments);
+ Tram.CallsApp = (function(superClass) {
+ extend(CallsApp, superClass);
+ return CallsApp.__super__.constructor.apply(this, arguments);
+ CallsApp.prototype.initialize = function() {
+ this.listenTo(this.collection, 'add', this.onAdd);
+ return this.listenTo(this.collection, 'remove', this.onRemove);
+ CallsApp.prototype.onAdd = function(model) {
+ model.view = new Tram.CallView({
+ return this.$el.append(model.view.render().el);
+ CallsApp.prototype.onRemove = function(model) {
+ return model.view.remove();
+//# sourceMappingURL=calls.js.map
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/js/calls.js.map Mon Mar 28 02:28:08 2016 +0800
+ "mappings": "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"
\ No newline at end of file