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. 6
      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. 8
      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. 8
      static/js/directives/screenshare.js
  28. 502
      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. 8
      static/js/services/buddylist.js
  33. 27
      static/js/services/constraints.js
  34. 4
      static/js/services/mediastream.js
  35. 242
      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. 43
      static/partials/settings.html
  42. 2
      static/translation/messages-de.json

7
.travis.yml

@ -10,7 +10,8 @@ go: @@ -10,7 +10,8 @@ go:
- tip
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:
- sudo add-apt-repository -y ppa:chris-lea/node.js
@ -21,11 +22,13 @@ install: @@ -21,11 +22,13 @@ install:
- sudo gem1.9.1 install compass
- sudo gem1.9.1 install scss-lint
- 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:
- ./autogen.sh
- ./configure
- make get
- if [ "$USE_GODEPS" = "0" ]; then make get; fi
- if [ "$USE_GODEPS" = "1" ]; then make gpm; fi
- make styleshint
# TODO(fancycode): enable styleslint once all styles have been fixed
# - make styleslint

11
Godeps

@ -0,0 +1,11 @@ @@ -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 @@ -42,7 +42,7 @@ DIST_BIN := $(DIST)/bin
all: build
build: binary assets
build: get binary assets
gopath:
@echo GOPATH=$(GOPATH)
@ -59,7 +59,11 @@ endif @@ -59,7 +59,11 @@ endif
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)
binaryrace: GOBUILDFLAGS := $(GOBUILDFLAGS) -race

7
configure.ac

@ -57,6 +57,13 @@ AC_PROG_AWK @@ -57,6 +57,13 @@ AC_PROG_AWK
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])
if test x"${JSHINT}" != x"" ; then
AC_MSG_CHECKING([for version of jshint])

18
debian/changelog vendored

@ -1,3 +1,21 @@ @@ -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
* Fixed javascript load order, so compiled scripts load properly.

1
server.conf.in

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

7
src/i18n/messages-de.po

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

6
src/i18n/messages-ja.po

@ -8,7 +8,7 @@ msgid "" @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\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"
"Last-Translator: Curt Frisemo <curt.frisemo@spreed.com>\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -357,6 +357,10 @@ msgstr "デフォルト・ルーム" @@ -357,6 +357,10 @@ msgstr "デフォルト・ルーム"
msgid "Set alternative room to join at start."
msgstr "スタート時に別のルームに参加する."
#, fuzzy
msgid "Notification sounds"
msgstr "デスクトップ通知"
msgid "Desktop notification"
msgstr "デスクトップ通知"

6
src/i18n/messages-ko.po

@ -8,7 +8,7 @@ msgid "" @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\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"
"Last-Translator: Curt Frisemo <curt.frisemo@spreed.com>\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -357,6 +357,10 @@ msgstr "기본 방" @@ -357,6 +357,10 @@ msgstr "기본 방"
msgid "Set alternative room to join at start."
msgstr "시작시에 다른 방에 합류하도록 설정 되었습니다"
#, fuzzy
msgid "Notification sounds"
msgstr "데스크탑에 통보"
msgid "Desktop notification"
msgstr "데스크탑에 통보"

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

@ -8,7 +8,7 @@ msgid "" @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\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"
"Last-Translator: Michael P.\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -357,6 +357,10 @@ msgstr "系统默认房间" @@ -357,6 +357,10 @@ msgstr "系统默认房间"
msgid "Set alternative room to join at start."
msgstr "重设初始默认房间"
#, fuzzy
msgid "Notification sounds"
msgstr "桌面提醒"
msgid "Desktop notification"
msgstr "桌面提醒"

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

@ -8,7 +8,7 @@ msgid "" @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\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"
"Last-Translator: Michael P.\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -357,6 +357,10 @@ msgstr "系統默認房間" @@ -357,6 +357,10 @@ msgstr "系統默認房間"
msgid "Set alternative room to join at start."
msgstr "重設初始默認房間"
#, fuzzy
msgid "Notification sounds"
msgstr "桌面提醒"
msgid "Desktop notification"
msgstr "桌面提醒"

5
src/i18n/messages.pot

@ -9,7 +9,7 @@ msgid "" @@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -350,6 +350,9 @@ msgstr "" @@ -350,6 +350,9 @@ msgstr ""
msgid "Set alternative room to join at start."
msgstr ""
msgid "Notification sounds"
msgstr ""
msgid "Desktop notification"
msgstr ""

14
src/styles/components/_audiovideo.scss

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

2
src/styles/components/_bar.scss

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

6
src/styles/components/_buddylist.scss

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

84
src/styles/components/_chat.scss

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

6
src/styles/components/_rightslide.scss

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

39
src/styles/components/_settings.scss

@ -24,13 +24,13 @@ @@ -24,13 +24,13 @@
background: $settings-background;
border-left: 1px solid $bordercolor;
bottom: 0;
padding-right: 20px;
padding-right: 0px;
position: fixed;
right: -520px;
top: $minbarheight;
top: 0;
transition: right 200ms ease-in-out;
width: 520px;
z-index: 50;
z-index: 80;
&.show {
right: 0;
@ -41,18 +41,6 @@ @@ -41,18 +41,6 @@
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 @@ @@ -62,15 +50,20 @@
left: 0;
overflow-x: hidden;
overflow-y: auto;
padding: 10px;
padding: 10px 15px;
position: absolute;
right: 0;
top: 0;
margin-top: 50px;
@include breakpt($breakpoint-settings-medium, max-width, only screen) {
padding-bottom: 10px;
}
legend {
font-size: ceil(($font-size-base * 1.25));
}
.version {
color: $settings-version;
font-size: 10px;
@ -79,20 +72,6 @@ @@ -79,20 +72,6 @@
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 {

15
src/styles/global/_base.scss

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

1
src/styles/global/_overlaybar.scss

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

21
src/styles/global/_variables.scss

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

8
static/js/controllers/appcontroller.js

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

40
static/js/controllers/uicontroller.js

@ -24,6 +24,12 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web @@ -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) {
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.
$($window).on("beforeunload", function(event) {
if (appData.flags.manualUnload || !$scope.peer) {
@ -82,7 +88,8 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web @@ -82,7 +88,8 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
"end": "end1",
"dial": "ringtone1",
"connect": "connect1",
"prompt": "question1"
"prompt": "question1",
"chatmessage": "message1"
});
var displayName = safeDisplayName;
@ -158,6 +165,16 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web @@ -158,6 +165,16 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
};
$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 autoAcceptTimeout = null;
$scope.updateAutoAccept = function(id, from) {
@ -225,6 +242,17 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web @@ -225,6 +242,17 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
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 dialer = playSound.interval("dial", null, 4000);
var dialerEnabled = false;
@ -708,13 +736,13 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web @@ -708,13 +736,13 @@ define(['jquery', 'underscore', 'bigscreen', 'moment', 'sjcl', 'modernizr', 'web
});
_.defer(function() {
if (!Modernizr.websockets) {
alertify.dialog.alert(translation._("Your browser is not supported. Please upgrade to a current version."));
$scope.setStatus("unsupported");
if (!$window.webrtcDetectedVersion || $window.webrtcDetectedBrowser === "edge") {
alertify.dialog.custom("webrtcUnsupported");
return;
}
if (!$window.webrtcDetectedVersion) {
alertify.dialog.alert(translation._("Your browser does not support WebRTC. No calls possible."));
if (!Modernizr.websockets || $window.webrtcDetectedVersion < $window.webrtcMinimumVersion) {
alertify.dialog.alert(translation._("Your browser is not supported. Please upgrade to a current version."));
$scope.setStatus("unsupported");
return;
}
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 @@ -25,7 +25,7 @@ define(['jquery', 'underscore', 'text!partials/buddypicturecapture.html'], funct
// buddyPictureCapture
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.
$scope.captureSize = {
@ -110,7 +110,7 @@ define(['jquery', 'underscore', 'text!partials/buddypicturecapture.html'], funct @@ -110,7 +110,7 @@ define(['jquery', 'underscore', 'text!partials/buddypicturecapture.html'], funct
var videoStop = function(stream, video) {
if (stream) {
video.pause();
stream.stop();
userMedia.stopUserMediaStream(stream);
stream = null;
}
};

2
static/js/directives/chat.js

@ -383,7 +383,7 @@ define(['jquery', 'underscore', 'text!partials/chat.html', 'text!partials/chatro @@ -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
// before we beep and shout.
if (!subscope.isgroupchat && from !== sessionid) {
playSound.play("message1");
playSound.play("chatmessage");
desktopNotify.notify(translation._("Message from ") + displayName(from), message);
appData.e.triggerHandler("uiNotification", ["chatmessage", {from: from, message: message, first: subscope.firstmessage}]);
}

8
static/js/directives/screenshare.js

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

502
static/js/libs/webrtc.adapter.js

@ -30,30 +30,62 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT @@ -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.
*/
var RTCPeerConnection = null;
var getUserMedia = null;
var attachMediaStream = null;
var reattachMediaStream = null;
var webrtcDetectedBrowser = 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) {
console.log('This appears to be Firefox');
if (typeof window === 'undefined' || !window.navigator) {
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';
// the detected firefox version.
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
// the minimum firefox version still supported by adapter.
webrtcMinimumVersion = 31;
// The RTCPeerConnection object.
window.RTCPeerConnection = function(pcConfig, pcConstraints) {
// .urls is not supported in FF yet.
if (pcConfig && pcConfig.iceServers) {
for (var i = 0; i < pcConfig.iceServers.length; i++) {
if (pcConfig.iceServers[i].hasOwnProperty('urls')) {
pcConfig.iceServers[i].url = pcConfig.iceServers[i].urls;
delete pcConfig.iceServers[i].urls;
if (webrtcDetectedVersion < 38) {
// .urls is not supported in FF < 38.
// create RTCIceServers with a single url.
if (pcConfig && pcConfig.iceServers) {
var newIceServers = [];
for (var i = 0; i < pcConfig.iceServers.length; i++) {
var server = pcConfig.iceServers[i];
if (server.hasOwnProperty('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);
@ -65,139 +97,407 @@ if (navigator.mozGetUserMedia) { @@ -65,139 +97,407 @@ if (navigator.mozGetUserMedia) {
// The RTCIceCandidate object.
window.RTCIceCandidate = mozRTCIceCandidate;
// getUserMedia shim (only difference is the prefix).
// Code from Adam Barth.
getUserMedia = navigator.mozGetUserMedia.bind(navigator);
navigator.getUserMedia = getUserMedia;
// Creates ICE server from the URL for FF.
window.createIceServer = function(url, username, password) {
var iceServer = null;
var urlParts = url.split(':');
if (urlParts[0].indexOf('stun') === 0) {
// 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
};
// getUserMedia constraints shim.
getUserMedia = function(constraints, onSuccess, onError) {
var constraintsToFF37 = function(c) {
if (typeof c !== 'object' || c.require) {
return c;
}
var require = [];
Object.keys(c).forEach(function(key) {
if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
return;
}
} else {
// FF 27 and above supports transport parameters in TURN url,
// So passing in the full url to create iceServer.
iceServer = {
'url': url,
'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 {
oc[key] = r.ideal;
}
c.advanced.push(oc);
delete r.ideal;
if (!Object.keys(r).length) {
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) {
var iceServers = [];
// Use .url for FireFox.
for (var i = 0; i < urls.length; i++) {
var iceServer =
window.createIceServer(urls[i], username, password);
if (iceServer !== null) {
iceServers.push(iceServer);
}
}
return iceServers;
navigator.getUserMedia = getUserMedia;
// Shim for mediaDevices on older versions.
if (!navigator.mediaDevices) {
navigator.mediaDevices = {getUserMedia: requestUserMedia,
addEventListener: function() { },
removeEventListener: function() { }
};
}
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.
attachMediaStream = function(element, stream) {
console.log('Attaching media stream');
element.mozSrcObject = stream;
};
reattachMediaStream = function(to, from) {
console.log('Reattaching media stream');
to.mozSrcObject = from.mozSrcObject;
};
} else if (navigator.webkitGetUserMedia) {
console.log('This appears to be Chrome');
webrtcUtils.log('This appears to be Chrome');
webrtcDetectedBrowser = 'chrome';
// Temporary fix until crbug/374263 is fixed.
// Setting Chrome version to 999, if version is unavailable.
var result = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
if (result !== null) {
webrtcDetectedVersion = parseInt(result[2], 10);
} else {
webrtcDetectedVersion = 999;
}
// Creates iceServer from the url for Chrome M33 and earlier.
window.createIceServer = function(url, username, password) {
var iceServer = null;
var urlParts = url.split(':');
if (urlParts[0].indexOf('stun') === 0) {
// Create iceServer with stun url.
iceServer = {
'url': url
// the detected chrome version.
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);
// the minimum chrome version still supported by adapter.
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);
}
var fixChromeStats = function(response) {
var standardReport = {};
var reports = response.result();
reports.forEach(function(report) {
var standardStats = {
id: report.id,
timestamp: report.timestamp,
type: report.type
};
report.names().forEach(function(name) {
standardStats[name] = report.stat(name);
});
standardReport[standardStats.id] = standardStats;
});
return standardReport;
};
} else if (urlParts[0].indexOf('turn') === 0) {
// Chrome M28 & above uses below TURN format.
iceServer = {
'url': url,
'credential': password,
'username': username
if (arguments.length >= 2) {
var successCallbackWrapper = function(response) {
args[1](fixChromeStats(response));
};
return origGetStats.apply(this, [successCallbackWrapper, arguments[0]]);
}
// promise-support
return new Promise(function(resolve, reject) {
origGetStats.apply(self, [resolve, reject]);
});
};
return pc;
};
// add promise support
['createOffer', 'createAnswer'].forEach(function(method) {
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]);
}
}]
);
});
};
});
// getUserMedia constraints shim.
var constraintsToChrome = function(c) {
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 iceServer;
return cc;
};
// Creates an ICEServer object from multiple URLs.
window.createIceServers = function(urls, username, password) {
return [{
'urls': urls,
'credential': password,
'username': username
}];
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;
// The RTCPeerConnection object.
RTCPeerConnection = function(pcConfig, pcConstraints) {
return new webkitRTCPeerConnection(pcConfig, pcConstraints);
};
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: ''};
}));
});
});
}};
}
// Get UserMedia (only difference is the prefix).
// Code from Adam Barth.
getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
navigator.getUserMedia = getUserMedia;
// 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.
attachMediaStream = function(element, stream) {
if (typeof element.srcObject !== 'undefined') {
element.srcObject = stream;
} else if (typeof element.mozSrcObject !== 'undefined') {
element.mozSrcObject = stream;
} else if (typeof element.src !== 'undefined') {
element.src = URL.createObjectURL(stream);
} else {
console.log('Error attaching stream to element.');
webrtcUtils.log('Error attaching stream to element.');
}
};
reattachMediaStream = function(to, from) {
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 {
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 @@ -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;
};
// Adapter to support navigator.mediaDevices API.
@ -119,6 +106,20 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr @@ -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.
var UserMedia = function(options) {
@ -214,7 +215,7 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr @@ -214,7 +215,7 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
clearTimeout(timeout);
timeout = null;
}
stream.stop();
stopUserMediaStream(stream);
if (complete.done) {
return;
}
@ -251,6 +252,8 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr @@ -251,6 +252,8 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
})({});
};
UserMedia.getUserMedia = getUserMedia;
UserMedia.stopUserMediaStream = stopUserMediaStream;
UserMedia.prototype.doGetUserMedia = function(currentcall, mediaConstraints) {
@ -308,7 +311,7 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr @@ -308,7 +311,7 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
console.log('User has granted access to local media.');
if (!this.started) {
stream.stop();
stopUserMediaStream(stream);
return;
}
@ -336,7 +339,7 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr @@ -336,7 +339,7 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
oldStream.onended = function() {
console.log("Silently ended replaced user media stream.");
};
oldStream.stop();
stopUserMediaStream(oldStream);
}
if (stream) {
@ -381,7 +384,7 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr @@ -381,7 +384,7 @@ define(['jquery', 'underscore', 'audiocontext', 'mediastream/dummystream', 'webr
this.audioSource = null;
}
if (this.localStream) {
this.localStream.stop()
stopUserMediaStream(this.localStream);
this.localStream = null;
}
if (this.audioProcessor) {

3
static/js/mediastream/webrtc.js

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

38
static/js/services/alertify.js

@ -89,7 +89,38 @@ define(["angular"], function(angular) { @@ -89,7 +89,38 @@ define(["angular"], function(angular) {
});
};
var registeredCustomDialog = [];
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) {
if (!message && title) {
message = title;
@ -104,6 +135,13 @@ define(["angular"], function(angular) { @@ -104,6 +135,13 @@ define(["angular"], function(angular) {
}
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) {
return dialog.exec("error", title, message, ok_cb, err_cb);
},

8
static/js/services/buddylist.js

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

27
static/js/services/constraints.js

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

4
static/js/services/mediastream.js

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

242
static/js/services/playsound.js

@ -22,141 +22,152 @@ @@ -22,141 +22,152 @@
"use strict";
define(['underscore', 'Howler', 'require'], function(_, Howler, require) {
var SoundInterval = function(sound, id, time) {
this.sound = sound;
this.id = id;
this.interval = null;
this.time = time;
};
SoundInterval.prototype.start = function() {
if (this.interval !== null) {
return;
}
var id = this.id;
var player = _.bind(function() {
return this.sound.play(id);
}, this);
player();
this.interval = setInterval(player, this.time);
};
SoundInterval.prototype.stop = function() {
clearInterval(this.interval);
this.interval = null;
delete this.sound.intervals[this.id];
};
var Sound = function(options, aliases) {
this.sound = null;
this.intervals = {};
if (options) {
this.initialize(options, aliases);
}
// Active initialized sound instances are kept here.
var registry = {};
var disabled = {};
window.PLAYSOUND = registry; // make available for debug.
};
// playSound
return [function() {
Sound.prototype.initialize = function(options, aliases) {
var SoundInterval = function(sound, id, time) {
this.sound = sound;
this.id = id;
this.interval = null;
this.time = time;
};
SoundInterval.prototype.start = function() {
if (this.interval !== null) {
return;
}
var id = this.id;
var player = _.bind(function() {
return this.sound.play(id);
}, this);
player();
this.interval = setInterval(player, this.time);
};
SoundInterval.prototype.stop = function() {
clearInterval(this.interval);
this.interval = null;
delete this.sound.intervals[this.id];
};
// Kill all the existing stuff if any.
if (this.sound) {
this.sound.stop();
}
_.each(this.intervals, function(i) {
i.stop();
});
this.intervals = {};
// Add error handler.
var onloaderror = options.onloaderror;
options.onloaderror = function(event) {
console.error("Failed to load sounds", event);
if (onloaderror) {
onloaderror.apply(this, arguments);
var Sound = function(options, aliases) {
this.sound = null;
this.intervals = {};
if (options) {
this.initialize(options, aliases);
}
};
// Replace urls with their require generated URLs.
var urls = options.urls;
if (urls) {
var new_urls = [];
_.each(urls, function(u) {
u = require.toUrl(u);
new_urls.push(u);
Sound.prototype.initialize = function(options, aliases) {
// Kill all the existing stuff if any.
if (this.sound) {
this.sound.stop();
}
_.each(this.intervals, function(i) {
i.stop();
});
options.urls = new_urls;
}
this.intervals = {};
// Add error handler.
var onloaderror = options.onloaderror;
options.onloaderror = function(event) {
console.error("Failed to load sounds", event);
if (onloaderror) {
onloaderror.apply(this, arguments);
}
};
// Replace urls with their require generated URLs.
var urls = options.urls;
if (urls) {
var new_urls = [];
_.each(urls, function(u) {
u = require.toUrl(u);
new_urls.push(u);
});
options.urls = new_urls;
}
// Create the new shit.
this.players = {};
this.aliases = _.extend({}, aliases);
this.sound = new Howler.Howl(options);
// Create the new shit.
this.players = {};
this.aliases = _.extend({}, aliases);
this.sound = new Howler.Howl(options);
return this;
return this;
};
};
Sound.prototype.getId = function(id) {
Sound.prototype.getId = function(id) {
if (this.aliases.hasOwnProperty(id)) {
return this.aliases[id];
}
return id;
if (this.aliases.hasOwnProperty(id)) {
return this.aliases[id];
}
return id;
};
};
Sound.prototype.play = function(name, interval, autostart) {
Sound.prototype.play = function(id, interval, autostart) {
if (!this.sound) {
console.log("Play sound but not initialized.", name);
return null;
}
if (!this.sound) {
console.log("Play sound but not initialized.", id);
return null;
}
var id = this.getId(name);
id = this.getId(id);
if (interval) {
if (interval) {
if (this.intervals.hasOwnProperty(id)) {
return this.intervals[id];
}
var i = this.intervals[id] = new SoundInterval(this, id, interval);
if (autostart) {
i.start();
}
return i;
if (this.intervals.hasOwnProperty(id)) {
return this.intervals[id];
}
var i = this.intervals[id] = new SoundInterval(this, id, interval);
if (autostart) {
i.start();
}
return i;
} else {
var player = this.players[id];
var sound = this.sound;
if (!player) {
player = this.players[id] = (function(id) {
var data = {};
var cb = function(soundId) {
data.soundId = soundId;
};
var play = _.debounce(function() {
if (data.soundId) {
sound.stop(data.soundId);
data.soundId = null;
}
sound.play(id, cb);
}, 10);
return play;
}(id));
}
player()
} else {
}
if (!this.shouldPlaySound(name) || !this.shouldPlaySound(id)) {
return;
}
};
var player = this.players[id];
var sound = this.sound;
if (!player) {
player = this.players[id] = (function(id) {
var data = {};
var cb = function(soundId) {
data.soundId = soundId;
};
var play = _.debounce(function() {
if (data.soundId) {
sound.stop(data.soundId);
data.soundId = null;
}
sound.play(id, cb);
}, 10);
return play;
}(id));
}
player()
// Active initialized sound instances are kept here.
var registry = {};
window.PLAYSOUND = registry; // make available for debug.
}
// playSound
return [function() {
};
Sound.prototype.shouldPlaySound = function(id) {
if (disabled.all || disabled.hasOwnProperty(id)) {
return false;
}
return true;
};
return {
initialize: function(options, name, aliases) {
@ -191,6 +202,13 @@ define(['underscore', 'Howler', 'require'], function(_, Howler, require) { @@ -191,6 +202,13 @@ define(['underscore', 'Howler', 'require'], function(_, Howler, require) {
time = 1500;
}
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([ @@ -69,7 +69,8 @@ define([
'services/modules',
'services/mediadevices',
'services/sandbox',
'services/dummystream'], function(_,
'services/dummystream',
'services/usermedia'], function(_,
desktopNotify,
playSound,
safeApply,
@ -116,7 +117,8 @@ constraints, @@ -116,7 +117,8 @@ constraints,
modules,
mediaDevices,
sandbox,
dummyStream) {
dummyStream,
userMedia) {
var services = {
desktopNotify: desktopNotify,
@ -165,7 +167,8 @@ dummyStream) { @@ -165,7 +167,8 @@ dummyStream) {
modules: modules,
mediaDevices: mediaDevices,
sandbox: sandbox,
dummyStream: dummyStream
dummyStream: dummyStream,
userMedia: userMedia
};
var initialize = function(angModule) {

36
static/js/services/usermedia.js

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

1
static/partials/chat.html

@ -7,7 +7,6 @@ @@ -7,7 +7,6 @@
<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}">
<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 chat")}} {{currentRoomName}}</span>
<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 @@ @@ -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="chatmenu">
<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 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-primary btn-locationshare" title="{{_('Share my location')}}" ng-click="shareGeolocation()"><i class="fa fa-location-arrow 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-default btn-fileupload" title="{{_('Upload files')}}"><i class="fa fa-upload 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 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>

43
static/partials/settings.html

@ -1,3 +1,14 @@ @@ -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="version">{{version}}</div>
<div ng-form="settingsform" class="form-horizontal" on-enter="saveSettings(user)" on-escape="cancelSettings()"
@ -24,7 +35,8 @@ @@ -24,7 +35,8 @@
</div>
</div>
<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>
</div>
</div>
@ -110,6 +122,7 @@ @@ -110,6 +122,7 @@
<span class="help-block">{{_('Set alternative room to join at start.')}}</span>
</div>
</div>
<legend>{{_('Notifications')}}</legend>
<div class="form-group" ng-show="desktopNotify.supported">
<label class="col-xs-4 control-label">{{_('Desktop notification')}}</label>
<div class="col-xs-8">
@ -120,6 +133,24 @@ @@ -120,6 +133,24 @@
</span>
</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-extra settings-extra></settings-extra>
@ -258,8 +289,10 @@ @@ -258,8 +289,10 @@
</settings-advanced>
<hr/>
<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">
<a ng-click="showAdvancedSettings = !showAdvancedSettings"><span ng-show="showAdvancedSettings">{{_('Show advanced settings')}}</span><span ng-hide="showAdvancedSettings">{{_('Hide advanced settings')}}</span></a>
</div>
@ -277,12 +310,8 @@ @@ -277,12 +310,8 @@
</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>
</div>
</div>
</div>

2
static/translation/messages-de.json

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