Browse Source

Merge branch 'release-0.24'

pull/222/merge v0.24.3
Simon Eisenmann 11 years ago
parent
commit
ad6891b0e2
  1. 7
      .travis.yml
  2. 11
      Godeps
  3. 8
      Makefile.am
  4. 7
      configure.ac
  5. 18
      debian/changelog
  6. 1
      server.conf.in
  7. 7
      src/i18n/messages-de.po
  8. 6
      src/i18n/messages-ja.po
  9. 6
      src/i18n/messages-ko.po
  10. 6
      src/i18n/messages-zh-cn.po
  11. 6
      src/i18n/messages-zh-tw.po
  12. 5
      src/i18n/messages.pot
  13. 14
      src/styles/components/_audiovideo.scss
  14. 2
      src/styles/components/_bar.scss
  15. 6
      src/styles/components/_buddylist.scss
  16. 84
      src/styles/components/_chat.scss
  17. 4
      src/styles/components/_rightslide.scss
  18. 39
      src/styles/components/_settings.scss
  19. 15
      src/styles/global/_base.scss
  20. 1
      src/styles/global/_overlaybar.scss
  21. 21
      src/styles/global/_variables.scss
  22. 2
      static/css/main.min.css
  23. 6
      static/js/controllers/appcontroller.js
  24. 40
      static/js/controllers/uicontroller.js
  25. 4
      static/js/directives/buddypicturecapture.js
  26. 2
      static/js/directives/chat.js
  27. 2
      static/js/directives/screenshare.js
  28. 486
      static/js/libs/webrtc.adapter.js
  29. 37
      static/js/mediastream/usermedia.js
  30. 3
      static/js/mediastream/webrtc.js
  31. 38
      static/js/services/alertify.js
  32. 6
      static/js/services/buddylist.js
  33. 27
      static/js/services/constraints.js
  34. 4
      static/js/services/mediastream.js
  35. 38
      static/js/services/playsound.js
  36. 9
      static/js/services/services.js
  37. 36
      static/js/services/usermedia.js
  38. 2
      static/js/services/videowaiter.js
  39. 1
      static/partials/chat.html
  40. 6
      static/partials/chatroom.html
  41. 41
      static/partials/settings.html
  42. 2
      static/translation/messages-de.json

7
.travis.yml

@ -10,7 +10,8 @@ go:
- tip - tip
env: env:
- GEM_HOME=/var/lib/gems/1.9.1 - GEM_HOME=/var/lib/gems/1.9.1 USE_GODEPS=0
- GEM_HOME=/var/lib/gems/1.9.1 USE_GODEPS=1
before_install: before_install:
- sudo add-apt-repository -y ppa:chris-lea/node.js - sudo add-apt-repository -y ppa:chris-lea/node.js
@ -21,11 +22,13 @@ install:
- sudo gem1.9.1 install compass - sudo gem1.9.1 install compass
- sudo gem1.9.1 install scss-lint - sudo gem1.9.1 install scss-lint
- npm install - npm install
- if [ "$USE_GODEPS" = "1" ]; then wget https://raw.githubusercontent.com/pote/gpm/v1.3.2/bin/gpm && chmod +x gpm && sudo mv gpm /usr/local/bin; fi
script: script:
- ./autogen.sh - ./autogen.sh
- ./configure - ./configure
- make get - if [ "$USE_GODEPS" = "0" ]; then make get; fi
- if [ "$USE_GODEPS" = "1" ]; then make gpm; fi
- make styleshint - make styleshint
# TODO(fancycode): enable styleslint once all styles have been fixed # TODO(fancycode): enable styleslint once all styles have been fixed
# - make styleslint # - make styleslint

11
Godeps

@ -0,0 +1,11 @@
github.com/gorilla/context 215affda49addc4c8ef7e2534915df2c8c35c6cd
github.com/gorilla/mux ba336c9cfb43552c90de6cb2ceedd3271c747558
github.com/gorilla/securecookie aeade84400a85c6875264ae51c7a56ecdcb61751
github.com/gorilla/websocket 6eb6ad425a89d9da7a5549bc6da8f79ba5c17844
github.com/longsleep/pkac 0.0.1
github.com/satori/go.uuid afe1e2ddf0f05b7c29d388a3f8e76cb15c2231ca
github.com/strukturag/goacceptlanguageparser goacceptlanguageparser_v100
github.com/strukturag/httputils httputils_v012
github.com/strukturag/phoenix phoenix_v0131
github.com/strukturag/sloth v0.9.2
code.google.com/p/goconf/... a4db5c465ed1

8
Makefile.am

@ -42,7 +42,7 @@ DIST_BIN := $(DIST)/bin
all: build all: build
build: binary assets build: get binary assets
gopath: gopath:
@echo GOPATH=$(GOPATH) @echo GOPATH=$(GOPATH)
@ -59,7 +59,11 @@ endif
getupdate: vendorclean get getupdate: vendorclean get
binary: get gpm:
@if [ "$(GPM)" = "" ]; then echo "Command 'gpm' not found"; exit 1; fi
$(GPM) install
binary:
$(GO) build $(GOBUILDFLAGS) -o bin/$(EXENAME) -ldflags '$(LDFLAGS)' app/$(EXENAME) $(GO) build $(GOBUILDFLAGS) -o bin/$(EXENAME) -ldflags '$(LDFLAGS)' app/$(EXENAME)
binaryrace: GOBUILDFLAGS := $(GOBUILDFLAGS) -race binaryrace: GOBUILDFLAGS := $(GOBUILDFLAGS) -race

7
configure.ac

@ -57,6 +57,13 @@ AC_PROG_AWK
AC_PATH_PROGS([FIND],[find]) AC_PATH_PROGS([FIND],[find])
AC_PATH_PROGS([GPM],[gpm])
if test x"${GPM}" != x"" ; then
AC_MSG_CHECKING([for version of gpm])
GPM_VERSION=`$GPM version 2>&1 | $SED 's/^>> gpm v//'`
AC_MSG_RESULT([$GPM_VERSION])
fi
AC_PATH_PROG([JSHINT],jshint, [], [$PWD/node_modules/.bin$PATH_SEPARATOR$PATH]) AC_PATH_PROG([JSHINT],jshint, [], [$PWD/node_modules/.bin$PATH_SEPARATOR$PATH])
if test x"${JSHINT}" != x"" ; then if test x"${JSHINT}" != x"" ; then
AC_MSG_CHECKING([for version of jshint]) AC_MSG_CHECKING([for version of jshint])

18
debian/changelog vendored

@ -1,3 +1,21 @@
spreed-webrtc-server (0.24.3) precise; urgency=low
* Removed deprecated API to fix Chromium 47 compatibility.
* Improved UI usability for smaller devices.
* Increased the width of buddy list and chat.
* Cleaned up sized, borders and default colors.
* Cleaned up chat ui.
* Chat animations no longer comnsume GPU power.
* Chat icons are now shown in their proper color again.
* Chat arrows are displayed properly again.
* Updated WebRTC adapter to latest version (fixing Chromium 45).
* Fixed CSP example for Chromium 45 and later.
* Added GPM Godebs file to track Golang dependencies.
* Fixed a problem where screen sharing streams were not cleaned up.
* Added support for custom type dialogs.
-- Simon Eisenmann <simon@struktur.de> Tue, 28 Jul 2015 19:22:28 +0200
spreed-webrtc-server (0.24.2) precise; urgency=low spreed-webrtc-server (0.24.2) precise; urgency=low
* Fixed javascript load order, so compiled scripts load properly. * Fixed javascript load order, so compiled scripts load properly.

1
server.conf.in

@ -118,6 +118,7 @@ serverRealm = local
; img-src 'self' data: blob:; ; img-src 'self' data: blob:;
; connect-src 'self' wss://server:port/ws blob:; ; connect-src 'self' wss://server:port/ws blob:;
; font-src 'self' data: blob:; ; font-src 'self' data: blob:;
; media-src 'self' blob:;
;contentSecurityPolicy = ;contentSecurityPolicy =
; Content-Security-Policy-Report-Only HTTP response header value. Use this ; Content-Security-Policy-Report-Only HTTP response header value. Use this
; to test your CSP before putting it into production. ; to test your CSP before putting it into production.

7
src/i18n/messages-de.po

@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n" "Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n" "Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2015-04-30 19:22+0200\n" "POT-Creation-Date: 2015-07-08 11:02+0200\n"
"PO-Revision-Date: 2015-04-30 19:27+0100\n" "PO-Revision-Date: 2015-07-08 11:02+0100\n"
"Last-Translator: Simon Eisenmann <simon@struktur.de>\n" "Last-Translator: Simon Eisenmann <simon@struktur.de>\n"
"Language-Team: struktur AG <opensource@struktur.de>\n" "Language-Team: struktur AG <opensource@struktur.de>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n"
@ -365,6 +365,9 @@ msgstr "Standard Raum"
msgid "Set alternative room to join at start." msgid "Set alternative room to join at start."
msgstr " Raum wird beim Start automatisch betreten." msgstr " Raum wird beim Start automatisch betreten."
msgid "Notification sounds"
msgstr "Klänge"
msgid "Desktop notification" msgid "Desktop notification"
msgstr "Desktop-Benachrichtigung" msgstr "Desktop-Benachrichtigung"

6
src/i18n/messages-ja.po

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n" "Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n" "Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2015-04-30 19:22+0200\n" "POT-Creation-Date: 2015-07-08 11:02+0200\n"
"PO-Revision-Date: 2014-04-23 22:25+0100\n" "PO-Revision-Date: 2014-04-23 22:25+0100\n"
"Last-Translator: Curt Frisemo <curt.frisemo@spreed.com>\n" "Last-Translator: Curt Frisemo <curt.frisemo@spreed.com>\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n" "Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -357,6 +357,10 @@ msgstr "デフォルト・ルーム"
msgid "Set alternative room to join at start." msgid "Set alternative room to join at start."
msgstr "スタート時に別のルームに参加する." msgstr "スタート時に別のルームに参加する."
#, fuzzy
msgid "Notification sounds"
msgstr "デスクトップ通知"
msgid "Desktop notification" msgid "Desktop notification"
msgstr "デスクトップ通知" msgstr "デスクトップ通知"

6
src/i18n/messages-ko.po

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n" "Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n" "Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2015-04-30 19:22+0200\n" "POT-Creation-Date: 2015-07-08 11:02+0200\n"
"PO-Revision-Date: 2014-04-13 20:30+0900\n" "PO-Revision-Date: 2014-04-13 20:30+0900\n"
"Last-Translator: Curt Frisemo <curt.frisemo@spreed.com>\n" "Last-Translator: Curt Frisemo <curt.frisemo@spreed.com>\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n" "Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -357,6 +357,10 @@ msgstr "기본 방"
msgid "Set alternative room to join at start." msgid "Set alternative room to join at start."
msgstr "시작시에 다른 방에 합류하도록 설정 되었습니다" msgstr "시작시에 다른 방에 합류하도록 설정 되었습니다"
#, fuzzy
msgid "Notification sounds"
msgstr "데스크탑에 통보"
msgid "Desktop notification" msgid "Desktop notification"
msgstr "데스크탑에 통보" msgstr "데스크탑에 통보"

6
src/i18n/messages-zh-cn.po

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n" "Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n" "Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2015-04-30 19:22+0200\n" "POT-Creation-Date: 2015-07-08 11:02+0200\n"
"PO-Revision-Date: 2014-05-21 09:54+0800\n" "PO-Revision-Date: 2014-05-21 09:54+0800\n"
"Last-Translator: Michael P.\n" "Last-Translator: Michael P.\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n" "Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -357,6 +357,10 @@ msgstr "系统默认房间"
msgid "Set alternative room to join at start." msgid "Set alternative room to join at start."
msgstr "重设初始默认房间" msgstr "重设初始默认房间"
#, fuzzy
msgid "Notification sounds"
msgstr "桌面提醒"
msgid "Desktop notification" msgid "Desktop notification"
msgstr "桌面提醒" msgstr "桌面提醒"

6
src/i18n/messages-zh-tw.po

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n" "Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n" "Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2015-04-30 19:22+0200\n" "POT-Creation-Date: 2015-07-08 11:02+0200\n"
"PO-Revision-Date: 2014-05-21 09:55+0800\n" "PO-Revision-Date: 2014-05-21 09:55+0800\n"
"Last-Translator: Michael P.\n" "Last-Translator: Michael P.\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n" "Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -357,6 +357,10 @@ msgstr "系統默認房間"
msgid "Set alternative room to join at start." msgid "Set alternative room to join at start."
msgstr "重設初始默認房間" msgstr "重設初始默認房間"
#, fuzzy
msgid "Notification sounds"
msgstr "桌面提醒"
msgid "Desktop notification" msgid "Desktop notification"
msgstr "桌面提醒" msgstr "桌面提醒"

5
src/i18n/messages.pot

@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n" "Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n" "Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2015-04-30 19:22+0200\n" "POT-Creation-Date: 2015-07-08 11:02+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -350,6 +350,9 @@ msgstr ""
msgid "Set alternative room to join at start." msgid "Set alternative room to join at start."
msgstr "" msgstr ""
msgid "Notification sounds"
msgstr ""
msgid "Desktop notification" msgid "Desktop notification"
msgstr "" msgstr ""

14
src/styles/components/_audiovideo.scss

@ -29,16 +29,22 @@
} }
} }
.withChat, .withChat {
// scss-lint:disable IdSelector
#audiovideo {
right: $chat-width;
}
}
.withBuddylist { .withBuddylist {
// scss-lint:disable IdSelector // scss-lint:disable IdSelector
#audiovideo { #audiovideo {
right: 260px; right: $buddylist-width;
} }
} }
.withBuddylist.withChat #audiovideo { // scss-lint:disable IdSelector .withBuddylist.withChat #audiovideo { // scss-lint:disable IdSelector
right: 520px; right: $chat-width + $buddylist-width;
} }
#audiovideo { // scss-lint:disable IdSelector #audiovideo { // scss-lint:disable IdSelector
@ -50,7 +56,7 @@
user-select: none; user-select: none;
@include breakpt($breakpoint-video-small, max-width, only screen) { @include breakpt($breakpoint-video-small, max-width, only screen) {
right: 0; right: 0 !important;
} }
&.fullscreen { &.fullscreen {

2
src/styles/components/_bar.scss

@ -216,6 +216,6 @@
animation-duration: 4s; animation-duration: 4s;
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-name: shakeityeah; animation-name: shakeityeah;
animation-timing-function: linear; animation-timing-function: steps(5);
transform-origin: 50% 50%; transform-origin: 50% 50%;
} }

6
src/styles/components/_buddylist.scss

@ -58,6 +58,9 @@
.withBuddylist #buddylist:before { // scss-lint:disable IdSelector .withBuddylist #buddylist:before { // scss-lint:disable IdSelector
content: '\f101'; content: '\f101';
padding-right: 0; padding-right: 0;
@include breakpt($breakpoint-medium, max-width) {
display: block;
}
} }
.withBuddylistAutoHide #buddylist:before { // scss-lint:disable IdSelector .withBuddylistAutoHide #buddylist:before { // scss-lint:disable IdSelector
@ -237,7 +240,8 @@
left: 65px; left: 65px;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
right: 0; right: 4px;
text-overflow: ellipsis;
top: 33px; top: 33px;
white-space: nowrap; white-space: nowrap;
} }

84
src/styles/components/_chat.scss

@ -21,13 +21,13 @@
#chat { // scss-lint:disable IdSelector #chat { // scss-lint:disable IdSelector
bottom: 0; bottom: 0;
min-width: $chat-width; width: $chat-width;
min-width: 200px;
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
position: absolute; position: absolute;
right: $chat-width; right: $buddylist-width;
top: 0; top: 0;
width: $chat-width;
z-index: 45; z-index: 45;
} }
@ -35,6 +35,11 @@
// scss-lint:disable IdSelector // scss-lint:disable IdSelector
#chat { #chat {
opacity: 1; opacity: 1;
@include breakpt($chat-width + 200, max-width) {
left: 0;
width: auto;
}
} }
&.withChatMaximized #chat { &.withChatMaximized #chat {
@ -42,6 +47,18 @@
width: auto; width: auto;
} }
&.withChatMaximized #chat .message {
@include breakpt($breakpoint-large, max-width) {
max-width: 55%;
}
@include breakpt($breakpoint-medium, max-width) {
max-width: 70%;
}
@include breakpt($breakpoint-small, max-width) {
max-width: 85%;
}
}
.chat { .chat {
pointer-events: auto; pointer-events: auto;
} }
@ -93,7 +110,7 @@
position: relative; position: relative;
&.newmessage { &.newmessage {
animation: newmessage 1s ease -.3s infinite; animation: newmessage 1s steps(1) infinite alternate;
} }
&.disabled { &.disabled {
@ -155,7 +172,7 @@
} }
.chatheader { .chatheader {
animation: newmessage 1s ease -.3s infinite; animation: newmessage 1s steps(1) infinite alternate;
} }
} }
@ -165,15 +182,15 @@
&.with_pictures .message { &.with_pictures .message {
&.is_self { &.is_self {
padding-right: 54px; padding-right: 34px;
.timestamp { .timestamp {
right: 58px; right: 45px;
} }
} }
&.is_remote { &.is_remote {
padding-left: 58px; padding-left: 44px;
} }
} }
@ -193,7 +210,7 @@
.typinghint { .typinghint {
color: $chat-typing; color: $chat-typing;
font-size: .8em; font-size: .8em;
height: 14px; height: 16px;
overflow: hidden; overflow: hidden;
padding: 0 6px; padding: 0 6px;
white-space: nowrap; white-space: nowrap;
@ -217,10 +234,6 @@
right: 6px; right: 6px;
top: 1px; top: 1px;
} }
> div {
border-top: 1px solid $bordercolor;
}
} }
.input { .input {
@ -286,9 +299,10 @@
clear: both; clear: both;
display: block; display: block;
margin: 0 4px 2px 18px; margin: 0 4px 2px 18px;
padding: 8px; padding: 8px 8px 4px 8px;
position: relative; position: relative;
word-wrap: break-word; word-wrap: break-word;
max-width: 85%;
ul { ul {
list-style-type: none; list-style-type: none;
@ -297,7 +311,6 @@
} }
.timestamp { .timestamp {
color: $chat-timestamp;
font-size: .8em; font-size: .8em;
position: absolute; position: absolute;
right: 8px; right: 8px;
@ -322,7 +335,7 @@
li { li {
line-height: 1.1em; line-height: 1.1em;
margin: 4px 0; margin: 0 0 4px 0;
padding-left: 1.2em; padding-left: 1.2em;
position: relative; position: relative;
@ -370,18 +383,19 @@
.buddyPicture { .buddyPicture {
background: $actioncolor1; background: $actioncolor1;
border-radius: 2px; border-radius: 2px;
height: 46px; font-size: .7em;
height: 30px;
left: 4px; left: 4px;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
text-align: center; text-align: center;
top: 4px; top: 4px;
width: 46px; width: 30px;
z-index: 0; z-index: 0;
.#{$fa-css-prefix} { .#{$fa-css-prefix} {
color: $actioncolor2; color: $actioncolor2;
line-height: 46px; line-height: 30px;
} }
img { img {
@ -406,7 +420,9 @@
} }
&.is_remote { &.is_remote {
float: left;
background: $chat-msg-remote-background; background: $chat-msg-remote-background;
color: $chat-msg-remote-color;
&:before { // arrow border &:before { // arrow border
border-color: transparent $chat-arrow-border; border-color: transparent $chat-arrow-border;
@ -426,15 +442,17 @@
} }
&.is_self { &.is_self {
float: right;
background: $chat-msg-self-background; background: $chat-msg-self-background;
color: $chat-msg-self-color;
margin-left: 4px; margin-left: 4px;
margin-right: 18px; margin-right: 18px;
padding-right: 0; padding-right: 4px;
&:before { // arrow border &:before { // arrow border
border-color: transparent $chat-arrow-border; border-color: transparent $chat-arrow-border;
border-width: 7px 0 7px 11px; border-width: 7px 0 7px 11px;
bottom: 4px; top: 4px;
bottom: auto; bottom: auto;
right: -12px; right: -12px;
} }
@ -442,13 +460,12 @@
&:after { // arrow background &:after { // arrow background
border-color: transparent $chat-msg-self-background; border-color: transparent $chat-msg-self-background;
border-width: 6px 0 6px 10px; border-width: 6px 0 6px 10px;
bottom: 5px; top: 5px;
bottom: auto; bottom: auto;
right: -11px; right: -11px;
} }
li:before { li:before {
color: $chat-msg-default-icon-color;
transform: scale(-1, 1); transform: scale(-1, 1);
} }
@ -469,7 +486,6 @@
z-index: initial; z-index: initial;
&:hover .buddyInfoActions { &:hover .buddyInfoActions {
height: 40px;
opacity: 1; opacity: 1;
} }
} }
@ -477,20 +493,23 @@
.buddyInfoActions { .buddyInfoActions {
cursor: default; cursor: default;
display: inline-block; display: inline-block;
height: 0; height: 40px;
left: 0; left: 0;
opacity: 0; opacity: 0;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
top: 48px; top: 32px;
transition: opacity 0.1s .1s linear, height .4s .1s ease-out; transition: opacity 0.1s .1s linear;
white-space: nowrap; white-space: nowrap;
z-index: 1; z-index: 1;
.btn-group { .btn-group {
display: block; display: block;
margin: 0 auto; margin: 0 auto;
width: 55px; width: 70px;
.btn {
width:35px;
}
} }
.btn-primary { .btn-primary {
@ -509,7 +528,7 @@
.chatmenu { .chatmenu {
height: 36px; height: 36px;
left: 0; left: 0;
padding: 2px 4px; padding: 4px;
position: absolute; position: absolute;
right: 0; right: 0;
top: 36px; top: 36px;
@ -520,7 +539,6 @@
} }
.chatbody { .chatbody {
border-left: 1px solid $bordercolor;
bottom: -1px; bottom: -1px;
left: 0; left: 0;
position: absolute; position: absolute;
@ -591,7 +609,7 @@
} }
@keyframes newmessage { @keyframes newmessage {
0% {background-color: $actioncolor1;} 0% {background-color: $actioncolor1; border-color: $actioncolor1;}
50% {background-color: $componentbg;} 50% {background-color: $componentbg; border-color: $componentbg;}
100% {background-color: $actioncolor1;} 100% {background-color: $actioncolor1; border-color: $actioncolor1;}
} }

4
src/styles/components/_rightslide.scss

@ -21,15 +21,17 @@
.withBuddylist #rightslide { // scss-lint:disable IdSelector .withBuddylist #rightslide { // scss-lint:disable IdSelector
@include breakpt($breakpoint-medium, min-width) {
right: 0; right: 0;
} }
}
#rightslide { // scss-lint:disable IdSelector #rightslide { // scss-lint:disable IdSelector
bottom: 0; bottom: 0;
left: 0; left: 0;
pointer-events: none; pointer-events: none;
position: absolute; position: absolute;
right: -260px; right: -1 * $buddylist-width;
top: $minbarheight; top: $minbarheight;
transition: right 200ms ease-in-out; transition: right 200ms ease-in-out;
z-index: 5; z-index: 5;

39
src/styles/components/_settings.scss

@ -24,13 +24,13 @@
background: $settings-background; background: $settings-background;
border-left: 1px solid $bordercolor; border-left: 1px solid $bordercolor;
bottom: 0; bottom: 0;
padding-right: 20px; padding-right: 0px;
position: fixed; position: fixed;
right: -520px; right: -520px;
top: $minbarheight; top: 0;
transition: right 200ms ease-in-out; transition: right 200ms ease-in-out;
width: 520px; width: 520px;
z-index: 50; z-index: 80;
&.show { &.show {
right: 0; right: 0;
@ -41,18 +41,6 @@
width: auto; width: auto;
} }
.form-actions {
@include breakpt($breakpoint-settings-medium, max-width, only screen) {
bottom: 0;
height: 60px;
left: 0;
margin-bottom: 0;
padding: 6px 0 6px 120px;
position: fixed;
right: 0;
}
}
} }
} }
@ -62,15 +50,20 @@
left: 0; left: 0;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
padding: 10px; padding: 10px 15px;
position: absolute; position: absolute;
right: 0; right: 0;
top: 0; top: 0;
margin-top: 50px;
@include breakpt($breakpoint-settings-medium, max-width, only screen) { @include breakpt($breakpoint-settings-medium, max-width, only screen) {
padding-bottom: 10px; padding-bottom: 10px;
} }
legend {
font-size: ceil(($font-size-base * 1.25));
}
.version { .version {
color: $settings-version; color: $settings-version;
font-size: 10px; font-size: 10px;
@ -79,20 +72,6 @@
top: 10px; top: 10px;
} }
.form-horizontal {
.controls {
@include breakpt($breakpoint-settings-medium, max-width, only screen) {
margin-left: 110px;
}
}
.control-label {
@include breakpt($breakpoint-settings-medium, max-width, only screen) {
width: 100px;
word-wrap: break-word;
}
}
}
} }
settings-advanced { settings-advanced {

15
src/styles/global/_base.scss

@ -29,9 +29,20 @@ body {
body { body {
margin: 0; margin: 0;
max-height: 100%; max-height: 100%;
max-width: 100%; overflow-x: hidden;
overflow: hidden; overflow-y: hidden;
padding: 0; padding: 0;
@include breakpt($breakpoint-medium, max-width) {
overflow-x: auto;
}
> .ui {
height: 100%;
min-width: $buddylist-width;
position: absolute;
width: 100%;
}
} }
a { a {

1
src/styles/global/_overlaybar.scss

@ -95,7 +95,6 @@
display: inline-block; display: inline-block;
margin-bottom: 0; margin-bottom: 0;
margin-left: .5em; margin-left: .5em;
max-width: 60%;
> * { > * {
padding-right: .5em; padding-right: .5em;

21
src/styles/global/_variables.scss

@ -32,6 +32,7 @@ $sidepanebg: #fff !default;
$bordercolor: #e7e7e7 !default; $bordercolor: #e7e7e7 !default;
$actioncolor1: rgb(132, 184, 25) !default; $actioncolor1: rgb(132, 184, 25) !default;
$actioncolor2: rgb(0, 149, 52) !default; $actioncolor2: rgb(0, 149, 52) !default;
$specialbg1: $background;
// branding // branding
$logo: url('../img/logo-small.png') !default; $logo: url('../img/logo-small.png') !default;
@ -53,9 +54,6 @@ $action-enable: $actioncolor1 !default;
$welcome: #aaa !default; $welcome: #aaa !default;
$loading: #ddd !default; $loading: #ddd !default;
// panes
$pane-width: 260px !default;
// font // font
$font-sans-serif: 'Helvetica Neue', Helvetica, Arial, sans-serif !default; $font-sans-serif: 'Helvetica Neue', Helvetica, Arial, sans-serif !default;
$base-font-size: 13px; // compass vertical_rhythm mixin $base-font-size: 13px; // compass vertical_rhythm mixin
@ -96,7 +94,7 @@ $video-overlayactions: rgba(0, 0, 0, .9) !default;
$settings-background: #fff !default; $settings-background: #fff !default;
// buddylist // buddylist
$buddylist-width: $pane-width !default; $buddylist-width: 300px !default;
$buddylist-background: $componentbg !default; $buddylist-background: $componentbg !default;
$buddylist-tab-color: rgba(0, 0, 0, .3) !default; $buddylist-tab-color: rgba(0, 0, 0, .3) !default;
$buddylist-tab-background: $componentbg !default; $buddylist-tab-background: $componentbg !default;
@ -106,18 +104,17 @@ $buddylist-buddy2: $componentfg2 !default;
$buddylist-action-font-size: 1.6em; $buddylist-action-font-size: 1.6em;
// chat // chat
$chat-width: $pane-width !default; $chat-width: 300px !default;
$chat-background: $componentbg !default; $chat-background: $specialbg1 !default;
$chat-header: rgba(255, 255, 255, .9) !default; $chat-header: rgba(255, 255, 255, .9) !default;
$chat-disabled: #aaa !default; $chat-disabled: #aaa !default;
$chat-badge: #84b819 !default; $chat-badge: #84b819 !default;
$chat-ctrl: rgba(0, 0, 0, .3) !default; $chat-ctrl: rgba(0, 0, 0, .3) !default;
$chat-timestamp: #aaa !default; $chat-meta: #666 !default;
$chat-meta: #aaa !default;
$chat-msg-background: #fff !default; $chat-msg-background: #fff !default;
$chat-msg-border: transparent !default; $chat-msg-border: transparent !default;
$chat-msg-shadow: rgba(0, 0, 0, .03) !default; $chat-msg-shadow: rgba(0, 0, 0, .03) !default;
$chat-arrow-border: #eee; $chat-arrow-border: #fff;
$chat-msg-default-icon-color: #ccc !default; $chat-msg-default-icon-color: #ccc !default;
$chat-msg-unread-icon-color: #fe9a2e !default; $chat-msg-unread-icon-color: #fe9a2e !default;
@ -135,9 +132,11 @@ $chat-msg-received-icon: '\f06e' !default;
$chat-msg-read-icon: '\f00c' !default; $chat-msg-read-icon: '\f00c' !default;
$chat-msg-self-background: #fff !default; $chat-msg-self-background: #fff !default;
$chat-msg-self-color: $font-color !default;
$chat-msg-remote-background: #fff !default; $chat-msg-remote-background: #fff !default;
$chat-msg-remote-color: $font-color !default;
$chat-bottom-background: $chat-background !default; $chat-bottom-background: transparent !default;
$chat-typing: $chat-meta !default; $chat-typing: $chat-meta !default;
$chat-input-border-color: #66afe9 !default; $chat-input-border-color: #66afe9 !default;
@ -173,7 +172,7 @@ $breakpoint-large: 1280px !default;
$breakpoint-video-small: 590px !default; $breakpoint-video-small: 590px !default;
$breakpoint-video-medium: 630px !default; $breakpoint-video-medium: 630px !default;
$breakpoint-chat-small: 210px !default; $breakpoint-chat-small: 210px !default;
$breakpoint-settings-medium: 630px !default; $breakpoint-settings-medium: 800px !default;
// touch specific // touch specific
$tap-highlight: rgba(0, 0, 0, 0) !default; $tap-highlight: rgba(0, 0, 0, 0) !default;

2
static/css/main.min.css vendored

File diff suppressed because one or more lines are too long

6
static/js/controllers/appcontroller.js

@ -55,6 +55,11 @@ define(["jquery", "angular", "underscore"], function($, angular, _) {
audioTypingNoiseDetection: true, audioTypingNoiseDetection: true,
videoLeakyBucket: true, videoLeakyBucket: true,
videoNoiseReduction: false videoNoiseReduction: false
},
sound: {
incomingMessages: true,
incomingCall: true,
roomJoinLeave: false
} }
} }
}; };
@ -66,6 +71,7 @@ define(["jquery", "angular", "underscore"], function($, angular, _) {
$scope.updateStatus(); $scope.updateStatus();
} }
$scope.refreshWebrtcSettings(); $scope.refreshWebrtcSettings();
$scope.refreshSoundSettings();
}; };
$scope.reset = function() { $scope.reset = function() {

40
static/js/controllers/uicontroller.js

@ -24,6 +24,12 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
return ["$scope", "$rootScope", "$element", "$window", "$timeout", "safeDisplayName", "safeApply", "mediaStream", "appData", "playSound", "desktopNotify", "alertify", "toastr", "translation", "fileDownload", "localStorage", "screensharing", "localStatus", "dialogs", "rooms", "constraints", function($scope, $rootScope, $element, $window, $timeout, safeDisplayName, safeApply, mediaStream, appData, playSound, desktopNotify, alertify, toastr, translation, fileDownload, localStorage, screensharing, localStatus, dialogs, rooms, constraints) { return ["$scope", "$rootScope", "$element", "$window", "$timeout", "safeDisplayName", "safeApply", "mediaStream", "appData", "playSound", "desktopNotify", "alertify", "toastr", "translation", "fileDownload", "localStorage", "screensharing", "localStatus", "dialogs", "rooms", "constraints", function($scope, $rootScope, $element, $window, $timeout, safeDisplayName, safeApply, mediaStream, appData, playSound, desktopNotify, alertify, toastr, translation, fileDownload, localStorage, screensharing, localStatus, dialogs, rooms, constraints) {
alertify.dialog.registerCustom({
baseType: 'notify',
type: 'webrtcUnsupported',
message: translation._("Your browser does not support WebRTC. No calls possible.")
});
// Avoid accidential reloads or exits when in a call. // Avoid accidential reloads or exits when in a call.
$($window).on("beforeunload", function(event) { $($window).on("beforeunload", function(event) {
if (appData.flags.manualUnload || !$scope.peer) { if (appData.flags.manualUnload || !$scope.peer) {
@ -82,7 +88,8 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
"end": "end1", "end": "end1",
"dial": "ringtone1", "dial": "ringtone1",
"connect": "connect1", "connect": "connect1",
"prompt": "question1" "prompt": "question1",
"chatmessage": "message1"
}); });
var displayName = safeDisplayName; var displayName = safeDisplayName;
@ -158,6 +165,16 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
}; };
$scope.refreshWebrtcSettings(); // Call once for bootstrap. $scope.refreshWebrtcSettings(); // Call once for bootstrap.
$scope.refreshSoundSettings = function() {
var s = $scope.master.settings.sound;
playSound.disable("chatmessage", !s.incomingMessages);
playSound.disable("ring", !s.incomingCall);
var roomJoinLeave = $scope.peer ? false : s.roomJoinLeave; // Do not play these sounds when in call.
playSound.disable("joined", !roomJoinLeave);
playSound.disable("left", !roomJoinLeave);
};
$scope.refreshSoundSettings(); // Call once on bootstrap;
var pickupTimeout = null; var pickupTimeout = null;
var autoAcceptTimeout = null; var autoAcceptTimeout = null;
$scope.updateAutoAccept = function(id, from) { $scope.updateAutoAccept = function(id, from) {
@ -225,6 +242,17 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
mediaStream.webrtc.setAudioMute(cameraMute); mediaStream.webrtc.setAudioMute(cameraMute);
}); });
$scope.$watch("peer", function(c, o) {
// Watch for peer and disable some sounds while there is a peer.
if (c && !o) {
// New call.
$scope.refreshSoundSettings();
} else if (!c && o) {
// No longer in call.
$scope.refreshSoundSettings();
}
});
var ringer = playSound.interval("ring", null, 4000); var ringer = playSound.interval("ring", null, 4000);
var dialer = playSound.interval("dial", null, 4000); var dialer = playSound.interval("dial", null, 4000);
var dialerEnabled = false; var dialerEnabled = false;
@ -708,13 +736,13 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
}); });
_.defer(function() { _.defer(function() {
if (!Modernizr.websockets) { if (!$window.webrtcDetectedVersion || $window.webrtcDetectedBrowser === "edge") {
alertify.dialog.alert(translation._("Your browser is not supported. Please upgrade to a current version.")); alertify.dialog.custom("webrtcUnsupported");
$scope.setStatus("unsupported");
return; return;
} }
if (!$window.webrtcDetectedVersion) { if (!Modernizr.websockets || $window.webrtcDetectedVersion < $window.webrtcMinimumVersion) {
alertify.dialog.alert(translation._("Your browser does not support WebRTC. No calls possible.")); alertify.dialog.alert(translation._("Your browser is not supported. Please upgrade to a current version."));
$scope.setStatus("unsupported");
return; return;
} }
if (mediaStream.config.Renegotiation && $window.webrtcDetectedBrowser === "firefox" && $window.webrtcDetectedVersion < 38) { if (mediaStream.config.Renegotiation && $window.webrtcDetectedBrowser === "firefox" && $window.webrtcDetectedVersion < 38) {

4
static/js/directives/buddypicturecapture.js

@ -25,7 +25,7 @@ define(['jquery', 'underscore', 'text!partials/buddypicturecapture.html'], funct
// buddyPictureCapture // buddyPictureCapture
return ["$compile", "$window", function($compile, $window) { return ["$compile", "$window", function($compile, $window) {
var controller = ['$scope', 'safeApply', '$timeout', '$q', "mediaDevices", function($scope, safeApply, $timeout, $q, mediaDevices) { var controller = ['$scope', 'safeApply', '$timeout', '$q', "mediaDevices", "userMedia", function($scope, safeApply, $timeout, $q, mediaDevices, userMedia) {
// Buddy picutre capture size. // Buddy picutre capture size.
$scope.captureSize = { $scope.captureSize = {
@ -110,7 +110,7 @@ define(['jquery', 'underscore', 'text!partials/buddypicturecapture.html'], funct
var videoStop = function(stream, video) { var videoStop = function(stream, video) {
if (stream) { if (stream) {
video.pause(); video.pause();
stream.stop(); userMedia.stopUserMediaStream(stream);
stream = null; stream = null;
} }
}; };

2
static/js/directives/chat.js

@ -383,7 +383,7 @@ define(['jquery', 'underscore', 'text!partials/chat.html', 'text!partials/chatro
// Make sure we are not in group chat or the message is from ourselves // Make sure we are not in group chat or the message is from ourselves
// before we beep and shout. // before we beep and shout.
if (!subscope.isgroupchat && from !== sessionid) { if (!subscope.isgroupchat && from !== sessionid) {
playSound.play("message1"); playSound.play("chatmessage");
desktopNotify.notify(translation._("Message from ") + displayName(from), message); desktopNotify.notify(translation._("Message from ") + displayName(from), message);
appData.e.triggerHandler("uiNotification", ["chatmessage", {from: from, message: message, first: subscope.firstmessage}]); appData.e.triggerHandler("uiNotification", ["chatmessage", {from: from, message: message, first: subscope.firstmessage}]);
} }

2
static/js/directives/screenshare.js

@ -95,9 +95,11 @@ define(['jquery', 'underscore', 'text!partials/screenshare.html', 'text!partials
mediaStream.webrtc.doSubscribeScreenshare(from, token, { mediaStream.webrtc.doSubscribeScreenshare(from, token, {
created: function(peerscreenshare) { created: function(peerscreenshare) {
peerscreenshare.e.on("remoteStreamAdded", function(event, stream) { peerscreenshare.e.on("remoteStreamAdded", function(event, stream) {
if (stream) {
$scope.$apply(function(scope) { $scope.$apply(function(scope) {
scope.addRemoteStream(stream, peerscreenshare); scope.addRemoteStream(stream, peerscreenshare);
}); });
}
}); });
peerscreenshare.e.on("remoteStreamRemoved", function(event, stream) { peerscreenshare.e.on("remoteStreamRemoved", function(event, stream) {
safeApply($scope, function(scope) { safeApply($scope, function(scope) {

486
static/js/libs/webrtc.adapter.js

@ -30,30 +30,62 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
var RTCPeerConnection = null;
var getUserMedia = null; var getUserMedia = null;
var attachMediaStream = null; var attachMediaStream = null;
var reattachMediaStream = null; var reattachMediaStream = null;
var webrtcDetectedBrowser = null; var webrtcDetectedBrowser = null;
var webrtcDetectedVersion = null; var webrtcDetectedVersion = null;
var webrtcMinimumVersion = null;
var webrtcUtils = {
log: function() {
// suppress console.log output when being included as a module.
if (!(typeof module !== 'undefined' ||
typeof require === 'function') && (typeof define === 'function')) {
console.log.apply(console, arguments);
}
}
};
if (navigator.mozGetUserMedia) { if (typeof window === 'undefined' || !window.navigator) {
console.log('This appears to be Firefox'); webrtcUtils.log('This does not appear to be a browser');
webrtcDetectedBrowser = 'not a browser';
} else if (navigator.mozGetUserMedia) {
webrtcUtils.log('This appears to be Firefox');
webrtcDetectedBrowser = 'firefox'; webrtcDetectedBrowser = 'firefox';
// the detected firefox version.
webrtcDetectedVersion = webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
// the minimum firefox version still supported by adapter.
webrtcMinimumVersion = 31;
// The RTCPeerConnection object. // The RTCPeerConnection object.
window.RTCPeerConnection = function(pcConfig, pcConstraints) { window.RTCPeerConnection = function(pcConfig, pcConstraints) {
// .urls is not supported in FF yet. if (webrtcDetectedVersion < 38) {
// .urls is not supported in FF < 38.
// create RTCIceServers with a single url.
if (pcConfig && pcConfig.iceServers) { if (pcConfig && pcConfig.iceServers) {
var newIceServers = [];
for (var i = 0; i < pcConfig.iceServers.length; i++) { for (var i = 0; i < pcConfig.iceServers.length; i++) {
if (pcConfig.iceServers[i].hasOwnProperty('urls')) { var server = pcConfig.iceServers[i];
pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls; if (server.hasOwnProperty('urls')) {
delete pcConfig.iceServers[i].urls; for (var j = 0; j < server.urls.length; j++) {
var newServer = {
url: server.urls[j]
};
if (server.urls[j].indexOf('turn') === 0) {
newServer.username = server.username;
newServer.credential = server.credential;
}
newIceServers.push(newServer);
} }
} else {
newIceServers.push(pcConfig.iceServers[i]);
}
}
pcConfig.iceServers = newIceServers;
} }
} }
return new mozRTCPeerConnection(pcConfig, pcConstraints); return new mozRTCPeerConnection(pcConfig, pcConstraints);
@ -65,139 +97,407 @@ if (navigator.mozGetUserMedia) {
// The RTCIceCandidate object. // The RTCIceCandidate object.
window.RTCIceCandidate = mozRTCIceCandidate; window.RTCIceCandidate = mozRTCIceCandidate;
// getUserMedia shim (only difference is the prefix). // getUserMedia constraints shim.
// Code from Adam Barth. getUserMedia = function(constraints, onSuccess, onError) {
getUserMedia = navigator.mozGetUserMedia.bind(navigator); var constraintsToFF37 = function(c) {
navigator.getUserMedia = getUserMedia; if (typeof c !== 'object' || c.require) {
return c;
// Creates ICE server from the URL for FF. }
window.createIceServer = function(url, username, password) { var require = [];
var iceServer = null; Object.keys(c).forEach(function(key) {
var urlParts = url.split(':'); if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
if (urlParts[0].indexOf('stun') === 0) { return;
// Create ICE server with STUN URL.
iceServer = {
'url': url
};
} else if (urlParts[0].indexOf('turn') === 0) {
if (webrtcDetectedVersion < 27) {
// Create iceServer with turn url.
// Ignore the transport parameter from TURN url for FF version <=27.
var turnUrlParts = url.split('?');
// Return null for createIceServer if transport=tcp.
if (turnUrlParts.length === 1 ||
turnUrlParts[1].indexOf('transport=udp') === 0) {
iceServer = {
'url': turnUrlParts[0],
'credential': password,
'username': username
};
} }
var r = c[key] = (typeof c[key] === 'object') ?
c[key] : {ideal: c[key]};
if (r.min !== undefined ||
r.max !== undefined || r.exact !== undefined) {
require.push(key);
}
if (r.exact !== undefined) {
if (typeof r.exact === 'number') {
r.min = r.max = r.exact;
} else {
c[key] = r.exact;
}
delete r.exact;
}
if (r.ideal !== undefined) {
c.advanced = c.advanced || [];
var oc = {};
if (typeof r.ideal === 'number') {
oc[key] = {min: r.ideal, max: r.ideal};
} else { } else {
// FF 27 and above supports transport parameters in TURN url, oc[key] = r.ideal;
// So passing in the full url to create iceServer. }
iceServer = { c.advanced.push(oc);
'url': url, delete r.ideal;
'credential': password, if (!Object.keys(r).length) {
'username': username delete c[key];
}
}
});
if (require.length) {
c.require = require;
}
return c;
}; };
if (webrtcDetectedVersion < 38) {
webrtcUtils.log('spec: ' + JSON.stringify(constraints));
if (constraints.audio) {
constraints.audio = constraintsToFF37(constraints.audio);
}
if (constraints.video) {
constraints.video = constraintsToFF37(constraints.video);
} }
webrtcUtils.log('ff37: ' + JSON.stringify(constraints));
} }
return iceServer; return navigator.mozGetUserMedia(constraints, onSuccess, onError);
}; };
window.createIceServers = function(urls, username, password) { navigator.getUserMedia = getUserMedia;
var iceServers = [];
// Use .url for FireFox. // Shim for mediaDevices on older versions.
for (var i = 0; i < urls.length; i++) { if (!navigator.mediaDevices) {
var iceServer = navigator.mediaDevices = {getUserMedia: requestUserMedia,
window.createIceServer(urls[i], username, password); addEventListener: function() { },
if (iceServer !== null) { removeEventListener: function() { }
iceServers.push(iceServer); };
}
} }
return iceServers; navigator.mediaDevices.enumerateDevices =
navigator.mediaDevices.enumerateDevices || function() {
return new Promise(function(resolve) {
var infos = [
{kind: 'audioinput', deviceId: 'default', label:'', groupId:''},
{kind: 'videoinput', deviceId: 'default', label:'', groupId:''}
];
resolve(infos);
});
}; };
if (webrtcDetectedVersion < 41) {
// Work around http://bugzil.la/1169665
var orgEnumerateDevices =
navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
navigator.mediaDevices.enumerateDevices = function() {
return orgEnumerateDevices().catch(function(e) {
if (e.name === 'NotFoundError') {
return [];
}
throw e;
});
};
}
// Attach a media stream to an element. // Attach a media stream to an element.
attachMediaStream = function(element, stream) { attachMediaStream = function(element, stream) {
console.log('Attaching media stream');
element.mozSrcObject = stream; element.mozSrcObject = stream;
}; };
reattachMediaStream = function(to, from) { reattachMediaStream = function(to, from) {
console.log('Reattaching media stream');
to.mozSrcObject = from.mozSrcObject; to.mozSrcObject = from.mozSrcObject;
}; };
} else if (navigator.webkitGetUserMedia) { } else if (navigator.webkitGetUserMedia) {
console.log('This appears to be Chrome'); webrtcUtils.log('This appears to be Chrome');
webrtcDetectedBrowser = 'chrome'; webrtcDetectedBrowser = 'chrome';
// Temporary fix until crbug/374263 is fixed.
// Setting Chrome version to 999, if version is unavailable. // the detected chrome version.
var result = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); webrtcDetectedVersion =
if (result !== null) { parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);
webrtcDetectedVersion = parseInt(result[2], 10);
} else { // the minimum chrome version still supported by adapter.
webrtcDetectedVersion = 999; webrtcMinimumVersion = 38;
// The RTCPeerConnection object.
window.RTCPeerConnection = function(pcConfig, pcConstraints) {
var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints);
var origGetStats = pc.getStats.bind(pc);
pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line
var self = this;
var args = arguments;
// If selector is a function then we are in the old style stats so just
// pass back the original getStats format to avoid breaking old users.
if (arguments.length > 0 && typeof selector === 'function') {
return origGetStats(selector, successCallback);
} }
// Creates iceServer from the url for Chrome M33 and earlier. var fixChromeStats = function(response) {
window.createIceServer = function(url, username, password) { var standardReport = {};
var iceServer = null; var reports = response.result();
var urlParts = url.split(':'); reports.forEach(function(report) {
if (urlParts[0].indexOf('stun') === 0) { var standardStats = {
// Create iceServer with stun url. id: report.id,
iceServer = { timestamp: report.timestamp,
'url': url type: report.type
}; };
} else if (urlParts[0].indexOf('turn') === 0) { report.names().forEach(function(name) {
// Chrome M28 & above uses below TURN format. standardStats[name] = report.stat(name);
iceServer = { });
'url': url, standardReport[standardStats.id] = standardStats;
'credential': password, });
'username': username
return standardReport;
}; };
if (arguments.length >= 2) {
var successCallbackWrapper = function(response) {
args[1](fixChromeStats(response));
};
return origGetStats.apply(this, [successCallbackWrapper, arguments[0]]);
} }
return iceServer;
// promise-support
return new Promise(function(resolve, reject) {
origGetStats.apply(self, [resolve, reject]);
});
}; };
// Creates an ICEServer object from multiple URLs. return pc;
window.createIceServers = function(urls, username, password) {
return [{
'urls': urls,
'credential': password,
'username': username
}];
}; };
// The RTCPeerConnection object. // add promise support
RTCPeerConnection = function(pcConfig, pcConstraints) { ['createOffer', 'createAnswer'].forEach(function(method) {
return new webkitRTCPeerConnection(pcConfig, pcConstraints); var nativeMethod = webkitRTCPeerConnection.prototype[method];
webkitRTCPeerConnection.prototype[method] = function() {
var self = this;
if (arguments.length < 1 || (arguments.length === 1 &&
typeof(arguments[0]) === 'object')) {
var opts = arguments.length === 1 ? arguments[0] : undefined;
return new Promise(function(resolve, reject) {
nativeMethod.apply(self, [resolve, reject, opts]);
});
} else {
return nativeMethod.apply(this, arguments);
}
};
});
['setLocalDescription', 'setRemoteDescription',
'addIceCandidate'].forEach(function(method) {
var nativeMethod = webkitRTCPeerConnection.prototype[method];
webkitRTCPeerConnection.prototype[method] = function() {
var args = arguments;
var self = this;
return new Promise(function(resolve, reject) {
nativeMethod.apply(self, [args[0],
function() {
resolve();
if (args.length >= 2) {
args[1].apply(null, []);
}
},
function(err) {
reject(err);
if (args.length >= 3) {
args[2].apply(null, [err]);
}
}]
);
});
}; };
});
// Get UserMedia (only difference is the prefix). // getUserMedia constraints shim.
// Code from Adam Barth. var constraintsToChrome = function(c) {
getUserMedia = navigator.webkitGetUserMedia.bind(navigator); if (typeof c !== 'object' || c.mandatory || c.optional) {
return c;
}
var cc = {};
Object.keys(c).forEach(function(key) {
if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
return;
}
var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
if (r.exact !== undefined && typeof r.exact === 'number') {
r.min = r.max = r.exact;
}
var oldname = function(prefix, name) {
if (prefix) {
return prefix + name.charAt(0).toUpperCase() + name.slice(1);
}
return (name === 'deviceId') ? 'sourceId' : name;
};
if (r.ideal !== undefined) {
cc.optional = cc.optional || [];
var oc = {};
if (typeof r.ideal === 'number') {
oc[oldname('min', key)] = r.ideal;
cc.optional.push(oc);
oc = {};
oc[oldname('max', key)] = r.ideal;
cc.optional.push(oc);
} else {
oc[oldname('', key)] = r.ideal;
cc.optional.push(oc);
}
}
if (r.exact !== undefined && typeof r.exact !== 'number') {
cc.mandatory = cc.mandatory || {};
cc.mandatory[oldname('', key)] = r.exact;
} else {
['min', 'max'].forEach(function(mix) {
if (r[mix] !== undefined) {
cc.mandatory = cc.mandatory || {};
cc.mandatory[oldname(mix, key)] = r[mix];
}
});
}
});
if (c.advanced) {
cc.optional = (cc.optional || []).concat(c.advanced);
}
return cc;
};
getUserMedia = function(constraints, onSuccess, onError) {
if (constraints.audio) {
constraints.audio = constraintsToChrome(constraints.audio);
}
if (constraints.video) {
constraints.video = constraintsToChrome(constraints.video);
}
webrtcUtils.log('chrome: ' + JSON.stringify(constraints));
return navigator.webkitGetUserMedia(constraints, onSuccess, onError);
};
navigator.getUserMedia = getUserMedia; navigator.getUserMedia = getUserMedia;
if (!navigator.mediaDevices) {
navigator.mediaDevices = {getUserMedia: requestUserMedia,
enumerateDevices: function() {
return new Promise(function(resolve) {
var kinds = {audio: 'audioinput', video: 'videoinput'};
return MediaStreamTrack.getSources(function(devices) {
resolve(devices.map(function(device) {
return {label: device.label,
kind: kinds[device.kind],
deviceId: device.id,
groupId: ''};
}));
});
});
}};
}
// A shim for getUserMedia method on the mediaDevices object.
// TODO(KaptenJansson) remove once implemented in Chrome stable.
if (!navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia = function(constraints) {
return requestUserMedia(constraints);
};
} else {
// Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
// function which returns a Promise, it does not accept spec-style
// constraints.
var origGetUserMedia = navigator.mediaDevices.getUserMedia.
bind(navigator.mediaDevices);
navigator.mediaDevices.getUserMedia = function(c) {
webrtcUtils.log('spec: ' + JSON.stringify(c)); // whitespace for alignment
c.audio = constraintsToChrome(c.audio);
c.video = constraintsToChrome(c.video);
webrtcUtils.log('chrome: ' + JSON.stringify(c));
return origGetUserMedia(c);
};
}
// Dummy devicechange event methods.
// TODO(KaptenJansson) remove once implemented in Chrome stable.
if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
navigator.mediaDevices.addEventListener = function() {
webrtcUtils.log('Dummy mediaDevices.addEventListener called.');
};
}
if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
navigator.mediaDevices.removeEventListener = function() {
webrtcUtils.log('Dummy mediaDevices.removeEventListener called.');
};
}
// Attach a media stream to an element. // Attach a media stream to an element.
attachMediaStream = function(element, stream) { attachMediaStream = function(element, stream) {
if (typeof element.srcObject !== 'undefined') { if (typeof element.srcObject !== 'undefined') {
element.srcObject = stream; element.srcObject = stream;
} else if (typeof element.mozSrcObject !== 'undefined') {
element.mozSrcObject = stream;
} else if (typeof element.src !== 'undefined') { } else if (typeof element.src !== 'undefined') {
element.src = URL.createObjectURL(stream); element.src = URL.createObjectURL(stream);
} else { } else {
console.log('Error attaching stream to element.'); webrtcUtils.log('Error attaching stream to element.');
} }
}; };
reattachMediaStream = function(to, from) { reattachMediaStream = function(to, from) {
to.src = from.src; to.src = from.src;
}; };
} else if (navigator.mediaDevices && navigator.userAgent.match(
/Edge\/(\d+).(\d+)$/)) {
webrtcUtils.log('This appears to be Edge');
webrtcDetectedBrowser = 'edge';
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10);
// the minimum version still supported by adapter.
webrtcMinimumVersion = 12;
getUserMedia = navigator.getUserMedia;
attachMediaStream = function(element, stream) {
element.srcObject = stream;
};
reattachMediaStream = function(to, from) {
to.srcObject = from.srcObject;
};
} else { } else {
console.log('Browser does not appear to be WebRTC-capable'); webrtcUtils.log('Browser does not appear to be WebRTC-capable');
}
// Returns the result of getUserMedia as a Promise.
function requestUserMedia(constraints) {
return new Promise(function(resolve, reject) {
getUserMedia(constraints, resolve, reject);
});
}
var webrtcTesting = {};
Object.defineProperty(webrtcTesting, 'version', {
set: function(version) {
webrtcDetectedVersion = version;
}
});
if (typeof module !== 'undefined') {
var RTCPeerConnection;
if (typeof window !== 'undefined') {
RTCPeerConnection = window.RTCPeerConnection;
}
module.exports = {
RTCPeerConnection: RTCPeerConnection,
getUserMedia: getUserMedia,
attachMediaStream: attachMediaStream,
reattachMediaStream: reattachMediaStream,
webrtcDetectedBrowser: webrtcDetectedBrowser,
webrtcDetectedVersion: webrtcDetectedVersion,
webrtcMinimumVersion: webrtcMinimumVersion,
webrtcTesting: webrtcTesting
//requestUserMedia: not exposed on purpose.
//trace: not exposed on purpose.
};
} else if ((typeof require === 'function') && (typeof define === 'function')) {
// Expose objects and functions when RequireJS is doing the loading.
define([], function() {
return {
RTCPeerConnection: window.RTCPeerConnection,
getUserMedia: getUserMedia,
attachMediaStream: attachMediaStream,
reattachMediaStream: reattachMediaStream,
webrtcDetectedBrowser: webrtcDetectedBrowser,
webrtcDetectedVersion: webrtcDetectedVersion,
webrtcMinimumVersion: webrtcMinimumVersion,
webrtcTesting: webrtcTesting
//requestUserMedia: not exposed on purpose.
//trace: not exposed on purpose.
};
});
} }

37
static/js/mediastream/usermedia.js

@ -83,19 +83,6 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
} }
} }
}); });
if (window.webrtcDetectedBrowser === "firefox" && window.webrtcDetectedVersion < 38) {
// Firefox < 38 needs a extra require field.
var r = [];
if (c.height) {
r.push("height");
}
if (c.width) {
r.push("width");
}
if (r.length) {
c.require = r;
}
}
return c; return c;
}; };
// Adapter to support navigator.mediaDevices API. // Adapter to support navigator.mediaDevices API.
@ -119,6 +106,20 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
} }
})(); })();
var stopUserMediaStream = (function() {
return function(stream) {
if (stream && stream.getTracks) {
var tracks = stream.getTracks();
_.each(tracks, function(t) {
t.stop();
});
} else {
console.warn("MediaStream.stop is deprecated");
stream.stop();
}
}
})();
// UserMedia. // UserMedia.
var UserMedia = function(options) { var UserMedia = function(options) {
@ -214,7 +215,7 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
clearTimeout(timeout); clearTimeout(timeout);
timeout = null; timeout = null;
} }
stream.stop(); stopUserMediaStream(stream);
if (complete.done) { if (complete.done) {
return; return;
} }
@ -251,6 +252,8 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
})({}); })({});
}; };
UserMedia.getUserMedia = getUserMedia;
UserMedia.stopUserMediaStream = stopUserMediaStream;
UserMedia.prototype.doGetUserMedia = function(currentcall, mediaConstraints) { UserMedia.prototype.doGetUserMedia = function(currentcall, mediaConstraints) {
@ -308,7 +311,7 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
console.log('User has granted access to local media.'); console.log('User has granted access to local media.');
if (!this.started) { if (!this.started) {
stream.stop(); stopUserMediaStream(stream);
return; return;
} }
@ -336,7 +339,7 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
oldStream.onended = function() { oldStream.onended = function() {
console.log("Silently ended replaced user media stream."); console.log("Silently ended replaced user media stream.");
}; };
oldStream.stop(); stopUserMediaStream(oldStream);
} }
if (stream) { if (stream) {
@ -381,7 +384,7 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
this.audioSource = null; this.audioSource = null;
} }
if (this.localStream) { if (this.localStream) {
this.localStream.stop() stopUserMediaStream(this.localStream);
this.localStream = null; this.localStream = null;
} }
if (this.audioProcessor) { if (this.audioProcessor) {

3
static/js/mediastream/webrtc.js

@ -357,9 +357,6 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
var success = function(stream) { var success = function(stream) {
console.info("testMediaAccess success"); console.info("testMediaAccess success");
if (stream) {
stream.stop();
}
cb(true); cb(true);
} }
var failed = function() { var failed = function() {

38
static/js/services/alertify.js

@ -89,7 +89,38 @@ define(["angular"], function(angular) {
}); });
}; };
var registeredCustomDialog = [];
var dialog = { var dialog = {
/**
* registerCustom registers a custom dialog. To overwrite an existing custom dialog simply use the same id.
*
* @param {Object} config Preferences for the custom dialog with the following properties:
* @param {String} config.baseType Existing dialog type to use as initial values and from which a template will be used.
* @param {String} config.type The ID of the custom dialog. The template name which is saved in $templateCache. If the the type is 'notify' the templateUrl must be '/dialogs/notify.html'.
* @param {String} [config.template] A custom template to use for the dialog instead of the baseType template.
* @param {String} [config.title] The title which the baseType modal dialog should display. If none is provided the baseType title is used.
* @param {String} [config.message] The message which the baseType modal dialog should display.
* @param {Function} [config.ok_cb] The callback function to be called on success.
* @param {Function} [config.err_cb] The callback function to be called on error.
*/
registerCustom: function(config) {
var conf = angular.extend({}, config);
if (!conf ||
conf && !conf.type ||
conf && !conf.baseType) {
throw Error("Custom template not configured correctly.");
}
var templateUrl = '/dialogs/' + conf.type + '.html';
if (conf.template) {
$templateCache.put(templateUrl, conf.template);
} else {
$templateCache.put(templateUrl, $templateCache.get('/dialogs/' + conf.baseType + '.html'));
}
if (!conf.title) {
conf.title = api.defaultMessages[conf.baseType];
}
registeredCustomDialog[conf.type] = conf;
},
exec: function(n, title, message, ok_cb, err_cb) { exec: function(n, title, message, ok_cb, err_cb) {
if (!message && title) { if (!message && title) {
message = title; message = title;
@ -104,6 +135,13 @@ define(["angular"], function(angular) {
} }
return dlg; return dlg;
}, },
custom: function(type) {
var config = registeredCustomDialog[type];
if (!config) {
throw new Error('The custom dialog type "' + type + '" is not registered.');
}
return dialog.exec(config.type, config.title, config.message, config.ok_cb, config.err_cb);
},
error: function(title, message, ok_cb, err_cb) { error: function(title, message, ok_cb, err_cb) {
return dialog.exec("error", title, message, ok_cb, err_cb); return dialog.exec("error", title, message, ok_cb, err_cb);
}, },

6
static/js/services/buddylist.js

@ -446,11 +446,9 @@ define(['jquery', 'angular', 'underscore', 'modernizr', 'avltree', 'text!partial
display.subline = ""; display.subline = "";
return; return;
} }
if (s.length > 20) {
display.sublineFull = s; display.sublineFull = s;
s = s.substr(0, 20) + "..."; if (s.length > 100) {
} else { s = s.substr(0, 100);
display.sublineFull = null;
} }
display.subline = s; display.subline = s;

27
static/js/services/constraints.js

@ -128,23 +128,24 @@
}; };
service.iceServers = function(constraints) { service.iceServers = function(constraints) {
var createIceServers = function(urls, username, password) {
var s = {
urls: urls
}
if (username) {
s.username = username;
s.credential = password;
}
return s;
};
var iceServers = []; var iceServers = [];
var iceServer;
if (service.stun && service.stun.length) { if (service.stun && service.stun.length) {
iceServer = $window.createIceServers(service.stun); iceServers.push(createIceServers(service.stun));
if (iceServer.length) {
iceServers.push.apply(iceServers, iceServer)
}
} }
if (service.turn && service.turn.urls && service.turn.urls.length) { if (service.turn && service.turn.urls && service.turn.urls.length) {
iceServer = $window.createIceServers(service.turn.urls, service.turn.username, service.turn.password); iceServers.push(createIceServers(service.turn.urls, service.turn.username, service.turn.password));
if (iceServer.length) {
iceServers.push.apply(iceServers, iceServer)
}
} }
webrtc.settings.pcConfig.iceServers = iceServers; webrtc.settings.pcConfig.iceServers = iceServers;
}; };
// Some default constraints. // Some default constraints.
@ -191,6 +192,7 @@
supported: (function() { supported: (function() {
var isChrome = $window.webrtcDetectedBrowser === "chrome"; var isChrome = $window.webrtcDetectedBrowser === "chrome";
var isFirefox = $window.webrtcDetectedBrowser === "firefox"; var isFirefox = $window.webrtcDetectedBrowser === "firefox";
var isEdge = $window.webrtcDetectedBrowser === "edge";
var version = $window.webrtcDetectedVersion; var version = $window.webrtcDetectedVersion;
// Constraints support table. // Constraints support table.
return { return {
@ -201,7 +203,8 @@
// Chrome supports this on Windows only. // Chrome supports this on Windows only.
renderToAssociatedSink: isChrome && $window.navigator.platform.indexOf("Win") === 0, renderToAssociatedSink: isChrome && $window.navigator.platform.indexOf("Win") === 0,
chrome: isChrome, chrome: isChrome,
firefox: isFirefox firefox: isFirefox,
edge: isEdge
}; };
})() })()
}; };

4
static/js/services/mediastream.js

@ -46,8 +46,8 @@ define([
// Apply configuration details. // Apply configuration details.
webrtc.settings.renegotiation = context.Cfg.Renegotiation && true; webrtc.settings.renegotiation = context.Cfg.Renegotiation && true;
if (webrtc.settings.renegotiation && $window.webrtcDetectedBrowser === "firefox") { if (webrtc.settings.renegotiation && $window.webrtcDetectedBrowser !== "chrome") {
console.warn("Disable renegotiation in Firefox for now."); console.warn("Disable renegotiation in anything but Chrome for now.");
webrtc.settings.renegotiation = false; webrtc.settings.renegotiation = false;
} }

38
static/js/services/playsound.js

@ -22,6 +22,14 @@
"use strict"; "use strict";
define(['underscore', 'Howler', 'require'], function(_, Howler, require) { define(['underscore', 'Howler', 'require'], function(_, Howler, require) {
// Active initialized sound instances are kept here.
var registry = {};
var disabled = {};
window.PLAYSOUND = registry; // make available for debug.
// playSound
return [function() {
var SoundInterval = function(sound, id, time) { var SoundInterval = function(sound, id, time) {
this.sound = sound; this.sound = sound;
this.id = id; this.id = id;
@ -104,15 +112,14 @@ define(['underscore', 'Howler', 'require'], function(_, Howler, require) {
}; };
Sound.prototype.play = function(name, interval, autostart) {
Sound.prototype.play = function(id, interval, autostart) {
if (!this.sound) { if (!this.sound) {
console.log("Play sound but not initialized.", id); console.log("Play sound but not initialized.", name);
return null; return null;
} }
id = this.getId(id); var id = this.getId(name);
if (interval) { if (interval) {
@ -127,6 +134,10 @@ define(['underscore', 'Howler', 'require'], function(_, Howler, require) {
} else { } else {
if (!this.shouldPlaySound(name) || !this.shouldPlaySound(id)) {
return;
}
var player = this.players[id]; var player = this.players[id];
var sound = this.sound; var sound = this.sound;
if (!player) { if (!player) {
@ -151,12 +162,12 @@ define(['underscore', 'Howler', 'require'], function(_, Howler, require) {
}; };
// Active initialized sound instances are kept here. Sound.prototype.shouldPlaySound = function(id) {
var registry = {}; if (disabled.all || disabled.hasOwnProperty(id)) {
window.PLAYSOUND = registry; // make available for debug. return false;
}
// playSound return true;
return [function() { };
return { return {
initialize: function(options, name, aliases) { initialize: function(options, name, aliases) {
@ -191,6 +202,13 @@ define(['underscore', 'Howler', 'require'], function(_, Howler, require) {
time = 1500; time = 1500;
} }
return s.play(id, time); return s.play(id, time);
},
disable: function(id, status) {
if (status !== false) {
disabled[id] = true;
} else {
delete disabled[id];
}
} }
} }

9
static/js/services/services.js

@ -69,7 +69,8 @@ define([
'services/modules', 'services/modules',
'services/mediadevices', 'services/mediadevices',
'services/sandbox', 'services/sandbox',
'services/dummystream'], function(_, 'services/dummystream',
'services/usermedia'], function(_,
desktopNotify, desktopNotify,
playSound, playSound,
safeApply, safeApply,
@ -116,7 +117,8 @@ constraints,
modules, modules,
mediaDevices, mediaDevices,
sandbox, sandbox,
dummyStream) { dummyStream,
userMedia) {
var services = { var services = {
desktopNotify: desktopNotify, desktopNotify: desktopNotify,
@ -165,7 +167,8 @@ dummyStream) {
modules: modules, modules: modules,
mediaDevices: mediaDevices, mediaDevices: mediaDevices,
sandbox: sandbox, sandbox: sandbox,
dummyStream: dummyStream dummyStream: dummyStream,
userMedia: userMedia
}; };
var initialize = function(angModule) { var initialize = function(angModule) {

36
static/js/services/usermedia.js

@ -0,0 +1,36 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
"use strict";
define(['mediastream/usermedia'], function(UserMedia) {
// userMedia
return [function() {
// Public api.
return {
getUserMedia: UserMedia.getUserMedia,
stopUserMediaStream: UserMedia.stopUserMediaStream
}
}];
});

2
static/js/services/videowaiter.js

@ -36,7 +36,7 @@ define(["underscore"], function(_) {
} }
return; return;
} }
var videoTracks = stream.getVideoTracks(); var videoTracks = stream && stream.getVideoTracks() || [];
//console.log("wait for video", videoTracks.length, video.currentTime, video.videoHeight, video); //console.log("wait for video", videoTracks.length, video.currentTime, video.videoHeight, video);
if (videoTracks.length === 0 && this.count >= 10) { if (videoTracks.length === 0 && this.count >= 10) {
cb(false, video, stream); cb(false, video, stream);

1
static/partials/chat.html

@ -7,7 +7,6 @@
<div class="list-group nicescroll"> <div class="list-group nicescroll">
<a ng-repeat="room in getVisibleRooms()" ng-click="activateRoom(room.id, true)" class="list-group-item" ng-class="{newmessage: room.pending, disabled: !room.enabled}"> <a ng-repeat="room in getVisibleRooms()" ng-click="activateRoom(room.id, true)" class="list-group-item" ng-class="{newmessage: room.pending, disabled: !room.enabled}">
<span class="badge" ng-show="room.pending">{{room.pending}}</span> <span class="badge" ng-show="room.pending">{{room.pending}}</span>
<i class="fa fa-lg" ng-class="{'fa-user': room.id !== '', 'fa-users': room.id === ''}"></i>
<span ng-if="room.id !== ''">{{room.id|displayName}}</span> <span ng-if="room.id !== ''">{{room.id|displayName}}</span>
<span ng-if="room.id === ''">{{_("Room chat")}} {{currentRoomName}}</span> <span ng-if="room.id === ''">{{_("Room chat")}} {{currentRoomName}}</span>
<button ng-if="room.id !== ''" class="btn btn-sm btn-default" ng-click="hideRoom(room.id)"> <button ng-if="room.id !== ''" class="btn btn-sm btn-default" ng-click="hideRoom(room.id)">

6
static/partials/chatroom.html

@ -2,9 +2,9 @@
<div class="chatheader"><div class="chatstatusicon" ng-click="deactivateRoom()"><i class="fa fa-angle-left"></i> <i class="fa fa fa-comments-o"></i></div><div class="chatheadertitle"><span ng-show="p2pstate" class="fa fa-exchange" title="{{_('Peer to peer')}}"/><span>{{settings.title}} {{id|displayName}}</span></div> <div class="ctrl"><i ng-hide="layout.chatMaximized" ng-click="toggleMax()" class="fa fa-expand"></i><i ng-show="layout.chatMaximized" ng-click="toggleMax()" class="fa fa-compress"></i><!--<i title="{{_('Close chat')}}" ng-click="hide()" class="fa fa-times"></i>--></div></div> <div class="chatheader"><div class="chatstatusicon" ng-click="deactivateRoom()"><i class="fa fa-angle-left"></i> <i class="fa fa fa-comments-o"></i></div><div class="chatheadertitle"><span ng-show="p2pstate" class="fa fa-exchange" title="{{_('Peer to peer')}}"/><span>{{settings.title}} {{id|displayName}}</span></div> <div class="ctrl"><i ng-hide="layout.chatMaximized" ng-click="toggleMax()" class="fa fa-expand"></i><i ng-show="layout.chatMaximized" ng-click="toggleMax()" class="fa fa-compress"></i><!--<i title="{{_('Close chat')}}" ng-click="hide()" class="fa fa-times"></i>--></div></div>
<div class="chatmenu"> <div class="chatmenu">
<div class="btn-group"> <div class="btn-group">
<button ng-if="!isgroupchat" class="btn btn-sm btn-primary" title="{{_('Start video call')}}" ng-click="doCall()"><i class="fa fa-phone fa-fw"></i></button> <button ng-if="!isgroupchat" class="btn btn-sm btn-default" title="{{_('Start video call')}}" ng-click="doCall()"><i class="fa fa-phone fa-fw"></i></button>
<button class="btn btn-sm btn-primary btn-fileupload" title="{{_('Upload files')}}"><i class="fa fa-upload fa-fw"></i></button> <button class="btn btn-sm btn-default btn-fileupload" title="{{_('Upload files')}}"><i class="fa fa-upload fa-fw"></i></button>
<button class="btn btn-sm btn-primary btn-locationshare" title="{{_('Share my location')}}" ng-click="shareGeolocation()"><i class="fa fa-location-arrow fa-fw"></i></button> <button class="btn btn-sm btn-default btn-locationshare" title="{{_('Share my location')}}" ng-click="shareGeolocation()"><i class="fa fa-location-arrow fa-fw"></i></button>
</div> </div>
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<button class="btn btn-sm btn-default" title="{{_('Clear chat')}}" ng-click="doClear()"><i class="fa fa-eraser fa-fw"></i></button> <button class="btn btn-sm btn-default" title="{{_('Clear chat')}}" ng-click="doClear()"><i class="fa fa-eraser fa-fw"></i></button>

41
static/partials/settings.html

@ -1,3 +1,14 @@
<div>
<nav class="navbar navbar-default navbar-static-top">
<div class="container-fluid">
<div>
<a class="navbar-brand">{{_('Settings')}}</a>
</div>
<div class="navbar-right">
<button ng-click="layout.settings=false" type="button" class="btn btn-primary navbar-btn">{{_('OK')}}</button>
</div>
</div>
</nav>
<div class="settings nicescroll"> <div class="settings nicescroll">
<div class="version">{{version}}</div> <div class="version">{{version}}</div>
<div ng-form="settingsform" class="form-horizontal" on-enter="saveSettings(user)" on-escape="cancelSettings()" <div ng-form="settingsform" class="form-horizontal" on-enter="saveSettings(user)" on-escape="cancelSettings()"
@ -24,7 +35,8 @@
</div> </div>
</div> </div>
<div class="form-group profile-publicnotice"> <div class="form-group profile-publicnotice">
<div class="col-xs-8 col-xs-offset-4"> <label class="col-xs-4 control-label"></label>
<div class="col-xs-8">
<p class="help-block">{{_('Your picture, name and status message identify yourself in calls, chats and rooms.')}}</p> <p class="help-block">{{_('Your picture, name and status message identify yourself in calls, chats and rooms.')}}</p>
</div> </div>
</div> </div>
@ -110,6 +122,7 @@
<span class="help-block">{{_('Set alternative room to join at start.')}}</span> <span class="help-block">{{_('Set alternative room to join at start.')}}</span>
</div> </div>
</div> </div>
<legend>{{_('Notifications')}}</legend>
<div class="form-group" ng-show="desktopNotify.supported"> <div class="form-group" ng-show="desktopNotify.supported">
<label class="col-xs-4 control-label">{{_('Desktop notification')}}</label> <label class="col-xs-4 control-label">{{_('Desktop notification')}}</label>
<div class="col-xs-8"> <div class="col-xs-8">
@ -120,6 +133,24 @@
</span> </span>
</div> </div>
</div> </div>
<div class="form-group">
<label class="col-xs-4 control-label"><input type="checkbox" ng-model="user.settings.sound.incomingMessages"/></label>
<div class="col-xs-8">
<div class="form-control-static">{{_('Sounds for incoming messages')}}</div>
</div>
</div>
<div class="form-group">
<label class="col-xs-4 control-label"><input type="checkbox" ng-model="user.settings.sound.incomingCall"/></label>
<div class="col-xs-8">
<div class="form-control-static">{{_('Ring on incoming calls')}}</div>
</div>
</div>
<div class="form-group">
<label class="col-xs-4 control-label"><input type="checkbox" ng-model="user.settings.sound.roomJoinLeave"/></label>
<div class="col-xs-8">
<div class="form-control-static">{{_('Sounds for users in current room')}}</div>
</div>
</div>
</settings-settings> </settings-settings>
<settings-extra settings-extra></settings-extra> <settings-extra settings-extra></settings-extra>
@ -258,6 +289,8 @@
</settings-advanced> </settings-advanced>
<hr/>
<div class="form-group"> <div class="form-group">
<div class="col-xs-4 control-label"></div> <div class="col-xs-4 control-label"></div>
<div class="col-xs-8"> <div class="col-xs-8">
@ -277,12 +310,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-group">
<div class="col-xs-8 col-xs-offset-4">
<a ng-click="layout.settings=false" class="btn btn-default">{{_('Close')}}</a>
</div>
</div>
</fieldset> </fieldset>
</div> </div>
</div> </div>
</div>

2
static/translation/messages-de.json

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save