+32
-10
@@ -96,6 +96,7 @@ html input[disabled] {
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
opacity: 0.65;
|
opacity: 0.65;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
filter: grayscale(50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
button.round {
|
button.round {
|
||||||
@@ -117,7 +118,7 @@ button.round.small {
|
|||||||
box-shadow: 0 2px 0.35rem 0 rgba(0, 0, 0, 0.25);
|
box-shadow: 0 2px 0.35rem 0 rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
button:focus, button:hover, button:active {
|
button:hover:not([disabled]), button:active:not([disabled]) {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
button:active {
|
button:active {
|
||||||
@@ -131,7 +132,7 @@ button.primary {
|
|||||||
background-color: #1e88e5; /* blue 600 */
|
background-color: #1e88e5; /* blue 600 */
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
button.primary:focus, button.primary:hover, button.primary:active {
|
button.primary:hover:not([disabled]), button.primary:active:not([disabled]) {
|
||||||
background-color: #2962ff;
|
background-color: #2962ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +140,7 @@ button.secondary {
|
|||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
color: #2196f3; /* blue 500 */
|
color: #2196f3; /* blue 500 */
|
||||||
}
|
}
|
||||||
button.secondary:focus, button.secondary:hover, button.secondary:active {
|
button.secondary:hover:not([disabled]), button.secondary:active:not([disabled]) {
|
||||||
background-color: #e3f2fd;
|
background-color: #e3f2fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +150,7 @@ button.outline {
|
|||||||
border: 1px solid silver;
|
border: 1px solid silver;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
button.outline:focus, button.outline:hover, button.outline:active {
|
button.outline:hover:not([disabled]), button.outline:active:not([disabled]) {
|
||||||
background-color: #e8eaf6; /* indigo 50 */
|
background-color: #e8eaf6; /* indigo 50 */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ button.danger {
|
|||||||
background-color: #ec6560;
|
background-color: #ec6560;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
button.danger:focus, button.danger:hover, button.danger:active {
|
button.danger:hover:not([disabled]), button.danger:active:not([disabled]) {
|
||||||
background-color: #f77;
|
background-color: #f77;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +166,7 @@ button.positive {
|
|||||||
background-color: #71c46f;
|
background-color: #71c46f;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
button.positive:focus, button.positive:hover, button.positive:active {
|
button.positive:hover:not([disabled]), button.positive:active:not([disabled]) {
|
||||||
background-color: rgb(127, 237, 127);
|
background-color: rgb(127, 237, 127);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1605,13 +1606,26 @@ img.channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
column-gap: 1rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 3.2rem;
|
width: 100%;
|
||||||
|
bottom: 2.5rem;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background-color: #eceff1;
|
}
|
||||||
box-shadow: 0px -2px 3px -1px rgba(0,0,0,0.1); /* Faint shadow above panel */
|
|
||||||
|
.incoming-call .controls {
|
||||||
|
bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls button {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
min-width: initial;
|
||||||
|
box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
#video-container-panel {
|
#video-container-panel {
|
||||||
@@ -1649,6 +1663,10 @@ img.channel {
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.call-party.self[disabled] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
video {
|
video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -1659,6 +1677,10 @@ video {
|
|||||||
transform: scaleX(-1);
|
transform: scaleX(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.call-party[disabled] video {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.caller-card {
|
.caller-card {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -1737,7 +1759,7 @@ video {
|
|||||||
left: initial;
|
left: initial;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 5rem 3.5rem 2rem 3.5rem;
|
padding: 4rem 3.5rem 5.5rem 3.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Generated
+64
-62
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "tinode-webapp",
|
"name": "tinode-webapp",
|
||||||
"version": "0.21.0-beta1",
|
"version": "0.21.0-beta2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "tinode-webapp",
|
"name": "tinode-webapp",
|
||||||
"version": "0.21.0-beta1",
|
"version": "0.21.0-beta2",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"firebase": "^9.14.0",
|
"firebase": "^9.14.0",
|
||||||
@@ -68,25 +68,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/core": {
|
"node_modules/@babel/core": {
|
||||||
"version": "7.20.7",
|
"version": "7.20.12",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz",
|
||||||
"integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==",
|
"integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.1.0",
|
"@ampproject/remapping": "^2.1.0",
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
"@babel/generator": "^7.20.7",
|
"@babel/generator": "^7.20.7",
|
||||||
"@babel/helper-compilation-targets": "^7.20.7",
|
"@babel/helper-compilation-targets": "^7.20.7",
|
||||||
"@babel/helper-module-transforms": "^7.20.7",
|
"@babel/helper-module-transforms": "^7.20.11",
|
||||||
"@babel/helpers": "^7.20.7",
|
"@babel/helpers": "^7.20.7",
|
||||||
"@babel/parser": "^7.20.7",
|
"@babel/parser": "^7.20.7",
|
||||||
"@babel/template": "^7.20.7",
|
"@babel/template": "^7.20.7",
|
||||||
"@babel/traverse": "^7.20.7",
|
"@babel/traverse": "^7.20.12",
|
||||||
"@babel/types": "^7.20.7",
|
"@babel/types": "^7.20.7",
|
||||||
"convert-source-map": "^1.7.0",
|
"convert-source-map": "^1.7.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"gensync": "^1.0.0-beta.2",
|
"gensync": "^1.0.0-beta.2",
|
||||||
"json5": "^2.2.1",
|
"json5": "^2.2.2",
|
||||||
"semver": "^6.3.0"
|
"semver": "^6.3.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -170,9 +170,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-create-class-features-plugin": {
|
"node_modules/@babel/helper-create-class-features-plugin": {
|
||||||
"version": "7.20.7",
|
"version": "7.20.12",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.12.tgz",
|
||||||
"integrity": "sha512-LtoWbDXOaidEf50hmdDqn9g8VEzsorMexoWMQdQODbvmqYmaF23pBP5VNPAGIFHsFQCIeKokDiz3CH5Y2jlY6w==",
|
"integrity": "sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-annotate-as-pure": "^7.18.6",
|
"@babel/helper-annotate-as-pure": "^7.18.6",
|
||||||
@@ -181,6 +181,7 @@
|
|||||||
"@babel/helper-member-expression-to-functions": "^7.20.7",
|
"@babel/helper-member-expression-to-functions": "^7.20.7",
|
||||||
"@babel/helper-optimise-call-expression": "^7.18.6",
|
"@babel/helper-optimise-call-expression": "^7.18.6",
|
||||||
"@babel/helper-replace-supers": "^7.20.7",
|
"@babel/helper-replace-supers": "^7.20.7",
|
||||||
|
"@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
|
||||||
"@babel/helper-split-export-declaration": "^7.18.6"
|
"@babel/helper-split-export-declaration": "^7.18.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -1698,9 +1699,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/traverse": {
|
"node_modules/@babel/traverse": {
|
||||||
"version": "7.20.10",
|
"version": "7.20.12",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.10.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz",
|
||||||
"integrity": "sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==",
|
"integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
@@ -3018,9 +3019,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "8.11.2",
|
"version": "8.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
||||||
"integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==",
|
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
@@ -3096,9 +3097,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/babel-loader": {
|
"node_modules/babel-loader": {
|
||||||
"version": "9.1.0",
|
"version": "9.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz",
|
||||||
"integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==",
|
"integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"find-cache-dir": "^3.3.2",
|
"find-cache-dir": "^3.3.2",
|
||||||
@@ -3301,9 +3302,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001441",
|
"version": "1.0.30001442",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz",
|
||||||
"integrity": "sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg==",
|
"integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3935,9 +3936,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fastq": {
|
"node_modules/fastq": {
|
||||||
"version": "1.14.0",
|
"version": "1.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
|
||||||
"integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
|
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
@@ -4869,9 +4870,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.20",
|
"version": "8.4.21",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
|
||||||
"integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==",
|
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -6392,9 +6393,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/webpack-cli/node_modules/commander": {
|
"node_modules/webpack-cli/node_modules/commander": {
|
||||||
"version": "9.4.1",
|
"version": "9.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
|
||||||
"integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==",
|
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.20.0 || >=14"
|
"node": "^12.20.0 || >=14"
|
||||||
@@ -6650,25 +6651,25 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/core": {
|
"@babel/core": {
|
||||||
"version": "7.20.7",
|
"version": "7.20.12",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz",
|
||||||
"integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==",
|
"integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ampproject/remapping": "^2.1.0",
|
"@ampproject/remapping": "^2.1.0",
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
"@babel/generator": "^7.20.7",
|
"@babel/generator": "^7.20.7",
|
||||||
"@babel/helper-compilation-targets": "^7.20.7",
|
"@babel/helper-compilation-targets": "^7.20.7",
|
||||||
"@babel/helper-module-transforms": "^7.20.7",
|
"@babel/helper-module-transforms": "^7.20.11",
|
||||||
"@babel/helpers": "^7.20.7",
|
"@babel/helpers": "^7.20.7",
|
||||||
"@babel/parser": "^7.20.7",
|
"@babel/parser": "^7.20.7",
|
||||||
"@babel/template": "^7.20.7",
|
"@babel/template": "^7.20.7",
|
||||||
"@babel/traverse": "^7.20.7",
|
"@babel/traverse": "^7.20.12",
|
||||||
"@babel/types": "^7.20.7",
|
"@babel/types": "^7.20.7",
|
||||||
"convert-source-map": "^1.7.0",
|
"convert-source-map": "^1.7.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"gensync": "^1.0.0-beta.2",
|
"gensync": "^1.0.0-beta.2",
|
||||||
"json5": "^2.2.1",
|
"json5": "^2.2.2",
|
||||||
"semver": "^6.3.0"
|
"semver": "^6.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6729,9 +6730,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-create-class-features-plugin": {
|
"@babel/helper-create-class-features-plugin": {
|
||||||
"version": "7.20.7",
|
"version": "7.20.12",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.12.tgz",
|
||||||
"integrity": "sha512-LtoWbDXOaidEf50hmdDqn9g8VEzsorMexoWMQdQODbvmqYmaF23pBP5VNPAGIFHsFQCIeKokDiz3CH5Y2jlY6w==",
|
"integrity": "sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-annotate-as-pure": "^7.18.6",
|
"@babel/helper-annotate-as-pure": "^7.18.6",
|
||||||
@@ -6740,6 +6741,7 @@
|
|||||||
"@babel/helper-member-expression-to-functions": "^7.20.7",
|
"@babel/helper-member-expression-to-functions": "^7.20.7",
|
||||||
"@babel/helper-optimise-call-expression": "^7.18.6",
|
"@babel/helper-optimise-call-expression": "^7.18.6",
|
||||||
"@babel/helper-replace-supers": "^7.20.7",
|
"@babel/helper-replace-supers": "^7.20.7",
|
||||||
|
"@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
|
||||||
"@babel/helper-split-export-declaration": "^7.18.6"
|
"@babel/helper-split-export-declaration": "^7.18.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -7771,9 +7773,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/traverse": {
|
"@babel/traverse": {
|
||||||
"version": "7.20.10",
|
"version": "7.20.12",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.10.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz",
|
||||||
"integrity": "sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==",
|
"integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
@@ -8889,9 +8891,9 @@
|
|||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "8.11.2",
|
"version": "8.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
||||||
"integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==",
|
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
@@ -8943,9 +8945,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"babel-loader": {
|
"babel-loader": {
|
||||||
"version": "9.1.0",
|
"version": "9.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz",
|
||||||
"integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==",
|
"integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"find-cache-dir": "^3.3.2",
|
"find-cache-dir": "^3.3.2",
|
||||||
@@ -9082,9 +9084,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"caniuse-lite": {
|
"caniuse-lite": {
|
||||||
"version": "1.0.30001441",
|
"version": "1.0.30001442",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz",
|
||||||
"integrity": "sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg==",
|
"integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
@@ -9545,9 +9547,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"fastq": {
|
"fastq": {
|
||||||
"version": "1.14.0",
|
"version": "1.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
|
||||||
"integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
|
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
@@ -10234,9 +10236,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"version": "8.4.20",
|
"version": "8.4.21",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
|
||||||
"integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==",
|
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "^3.3.4",
|
||||||
@@ -11283,9 +11285,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": {
|
"commander": {
|
||||||
"version": "9.4.1",
|
"version": "9.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
|
||||||
"integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==",
|
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "tinode-webapp",
|
"name": "tinode-webapp",
|
||||||
"description": "Tinode messenger for the web",
|
"description": "Tinode messenger for the web",
|
||||||
"version": "0.21.0-beta1",
|
"version": "0.21.0-beta2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/tinode/example-react-js.git"
|
"url": "https://github.com/tinode/example-react-js.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"start": "echo 'Read installation instructions'",
|
||||||
"build": "npm run vers && npm run build:css && npm run extract && npm run build:i18n && npm run build:prod && npm run build:dev",
|
"build": "npm run vers && npm run build:css && npm run extract && npm run build:i18n && npm run build:prod && npm run build:dev",
|
||||||
"clean": "rm umd/* && rm css/base.min.css && rm i18n.min/*",
|
"clean": "rm umd/* && rm css/base.min.css && rm i18n.min/*",
|
||||||
"build:css": "postcss css/base.css > css/base.min.css",
|
"build:css": "postcss css/base.css > css/base.min.css",
|
||||||
|
|||||||
+5
-5
@@ -64,16 +64,16 @@ const fbMessaging = firebase.messaging();
|
|||||||
|
|
||||||
// This method shows the push notifications while the window is in background.
|
// This method shows the push notifications while the window is in background.
|
||||||
fbMessaging.onBackgroundMessage(payload => {
|
fbMessaging.onBackgroundMessage(payload => {
|
||||||
if (payload.data.silent == 'true') {
|
|
||||||
// TODO: if this is an 'msg', fetch the new message in the background.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify webapp that a message was received.
|
// Notify webapp that a message was received.
|
||||||
if (webAppChannel) {
|
if (webAppChannel) {
|
||||||
webAppChannel.postMessage(payload.data);
|
webAppChannel.postMessage(payload.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payload.data.silent == 'true') {
|
||||||
|
// No need to show anything.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const titles = {'msg': 'new_message', 'sub': 'new_chat'};
|
const titles = {'msg': 'new_message', 'sub': 'new_chat'};
|
||||||
const pushType = payload.data.what || 'msg';
|
const pushType = payload.data.what || 'msg';
|
||||||
const title = payload.data.title || self.i18nMessage(titles[pushType]);
|
const title = payload.data.title || self.i18nMessage(titles[pushType]);
|
||||||
|
|||||||
+1
-1
@@ -4,5 +4,5 @@ export const CALL_STATE_OUTGOING_INITATED = 1;
|
|||||||
export const CALL_STATE_INCOMING_RECEIVED = 2;
|
export const CALL_STATE_INCOMING_RECEIVED = 2;
|
||||||
export const CALL_STATE_IN_PROGRESS = 3;
|
export const CALL_STATE_IN_PROGRESS = 3;
|
||||||
|
|
||||||
// Video call drafty headers representing call state.
|
// Video call message headers representing call state.
|
||||||
export const CALL_HEAD_STARTED = 'started';
|
export const CALL_HEAD_STARTED = 'started';
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1543,6 +1543,13 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
|
"menu_item_audio_call": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Call",
|
||||||
|
"description": "Start audio call",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
"unrecognized_video_format": {
|
"unrecognized_video_format": {
|
||||||
"translation": "Das Format dieses Videos wird nicht erkannt",
|
"translation": "Das Format dieses Videos wird nicht erkannt",
|
||||||
"defaultMessage": "Format of this video is not recognized",
|
"defaultMessage": "Format of this video is not recognized",
|
||||||
|
|||||||
@@ -1543,6 +1543,13 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
|
"menu_item_audio_call": {
|
||||||
|
"translation": "Call",
|
||||||
|
"defaultMessage": "Call",
|
||||||
|
"description": "Start audio call",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
"unrecognized_video_format": {
|
"unrecognized_video_format": {
|
||||||
"translation": "Format of this video is not recognized",
|
"translation": "Format of this video is not recognized",
|
||||||
"defaultMessage": "Format of this video is not recognized",
|
"defaultMessage": "Format of this video is not recognized",
|
||||||
|
|||||||
@@ -1543,6 +1543,13 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
|
"menu_item_audio_call": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Call",
|
||||||
|
"description": "Start audio call",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
"unrecognized_video_format": {
|
"unrecognized_video_format": {
|
||||||
"translation": "No se reconoce el formato de este video",
|
"translation": "No se reconoce el formato de este video",
|
||||||
"defaultMessage": "Format of this video is not recognized",
|
"defaultMessage": "Format of this video is not recognized",
|
||||||
|
|||||||
@@ -500,6 +500,10 @@
|
|||||||
"defaultMessage": "Archive",
|
"defaultMessage": "Archive",
|
||||||
"description": "Move topic from the list of active chats to archive"
|
"description": "Move topic from the list of active chats to archive"
|
||||||
},
|
},
|
||||||
|
"menu_item_audio_call": {
|
||||||
|
"defaultMessage": "Call",
|
||||||
|
"description": "Start audio call"
|
||||||
|
},
|
||||||
"menu_item_block": {
|
"menu_item_block": {
|
||||||
"defaultMessage": "Block",
|
"defaultMessage": "Block",
|
||||||
"description": "Block topic or user"
|
"description": "Block topic or user"
|
||||||
|
|||||||
@@ -1543,6 +1543,13 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
|
"menu_item_audio_call": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Call",
|
||||||
|
"description": "Start audio call",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
"unrecognized_video_format": {
|
"unrecognized_video_format": {
|
||||||
"translation": "Le format de cette vidéo n'est pas reconnu",
|
"translation": "Le format de cette vidéo n'est pas reconnu",
|
||||||
"defaultMessage": "Format of this video is not recognized",
|
"defaultMessage": "Format of this video is not recognized",
|
||||||
|
|||||||
@@ -1543,6 +1543,13 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
|
"menu_item_audio_call": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Call",
|
||||||
|
"description": "Start audio call",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
"unrecognized_video_format": {
|
"unrecognized_video_format": {
|
||||||
"translation": "이 동영상의 형식을 인식할 수 없습니다.",
|
"translation": "이 동영상의 형식을 인식할 수 없습니다.",
|
||||||
"defaultMessage": "Format of this video is not recognized",
|
"defaultMessage": "Format of this video is not recognized",
|
||||||
|
|||||||
@@ -1543,6 +1543,13 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
|
"menu_item_audio_call": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Call",
|
||||||
|
"description": "Start audio call",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
"unrecognized_video_format": {
|
"unrecognized_video_format": {
|
||||||
"translation": "Formatul acestui videoclip nu este recunoscut",
|
"translation": "Formatul acestui videoclip nu este recunoscut",
|
||||||
"defaultMessage": "Format of this video is not recognized",
|
"defaultMessage": "Format of this video is not recognized",
|
||||||
|
|||||||
@@ -1543,6 +1543,13 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
|
"menu_item_audio_call": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Call",
|
||||||
|
"description": "Start audio call",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
"unrecognized_video_format": {
|
"unrecognized_video_format": {
|
||||||
"translation": "Неизвестный формат видео файла",
|
"translation": "Неизвестный формат видео файла",
|
||||||
"defaultMessage": "Format of this video is not recognized",
|
"defaultMessage": "Format of this video is not recognized",
|
||||||
|
|||||||
@@ -1543,6 +1543,13 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
|
"menu_item_audio_call": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Call",
|
||||||
|
"description": "Start audio call",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
"unrecognized_video_format": {
|
"unrecognized_video_format": {
|
||||||
"translation": "無法識別此視頻的格式",
|
"translation": "無法識別此視頻的格式",
|
||||||
"defaultMessage": "Format of this video is not recognized",
|
"defaultMessage": "Format of this video is not recognized",
|
||||||
|
|||||||
@@ -1543,6 +1543,13 @@
|
|||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
},
|
},
|
||||||
|
"menu_item_audio_call": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Call",
|
||||||
|
"description": "Start audio call",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
"unrecognized_video_format": {
|
"unrecognized_video_format": {
|
||||||
"translation": "无法识别此视频的格式",
|
"translation": "无法识别此视频的格式",
|
||||||
"defaultMessage": "Format of this video is not recognized",
|
"defaultMessage": "Format of this video is not recognized",
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
// This is a generated file. Don't edit.
|
// This is a generated file. Don't edit.
|
||||||
|
|
||||||
export const PACKAGE_VERSION = "0.21.0-beta1";
|
export const PACKAGE_VERSION = "0.21.0-beta2";
|
||||||
|
|||||||
@@ -303,8 +303,6 @@ class MessagesView extends React.Component {
|
|||||||
scrollPosition: 0,
|
scrollPosition: 0,
|
||||||
fetchingMessages: false,
|
fetchingMessages: false,
|
||||||
showGoToLastButton: false,
|
showGoToLastButton: false,
|
||||||
forwardMessage: null,
|
|
||||||
reply: null,
|
|
||||||
contentToEdit: null,
|
contentToEdit: null,
|
||||||
dragging: false
|
dragging: false
|
||||||
};
|
};
|
||||||
@@ -315,6 +313,8 @@ class MessagesView extends React.Component {
|
|||||||
content: nextProps.forwardMessage.preview,
|
content: nextProps.forwardMessage.preview,
|
||||||
seq: null
|
seq: null
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
nextState.reply = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topic) {
|
if (topic) {
|
||||||
@@ -1358,6 +1358,7 @@ class MessagesView extends React.Component {
|
|||||||
topic={this.state.topic}
|
topic={this.state.topic}
|
||||||
seq={this.props.callSeq}
|
seq={this.props.callSeq}
|
||||||
callState={this.props.callState}
|
callState={this.props.callState}
|
||||||
|
callAudioOnly={this.props.callAudioOnly}
|
||||||
tinode={this.props.tinode}
|
tinode={this.props.tinode}
|
||||||
title={this.state.title}
|
title={this.state.title}
|
||||||
avatar={this.state.avatar || true}
|
avatar={this.state.avatar || true}
|
||||||
|
|||||||
+40
-12
@@ -69,6 +69,11 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Info',
|
defaultMessage: 'Info',
|
||||||
description: 'Show extended topic information'
|
description: 'Show extended topic information'
|
||||||
},
|
},
|
||||||
|
menu_item_audio_call: {
|
||||||
|
id: 'menu_item_audio_call',
|
||||||
|
defaultMessage: 'Call',
|
||||||
|
description: 'Start audio call'
|
||||||
|
},
|
||||||
menu_item_video_call: {
|
menu_item_video_call: {
|
||||||
id: 'menu_item_video_call',
|
id: 'menu_item_video_call',
|
||||||
defaultMessage: 'Video call',
|
defaultMessage: 'Video call',
|
||||||
@@ -158,6 +163,7 @@ class TinodeWeb extends React.Component {
|
|||||||
this.handleHideForwardDialog = this.handleHideForwardDialog.bind(this);
|
this.handleHideForwardDialog = this.handleHideForwardDialog.bind(this);
|
||||||
|
|
||||||
this.handleStartVideoCall = this.handleStartVideoCall.bind(this);
|
this.handleStartVideoCall = this.handleStartVideoCall.bind(this);
|
||||||
|
this.handleStartAudioCall = this.handleStartAudioCall.bind(this);
|
||||||
this.handleInfoMessage = this.handleInfoMessage.bind(this);
|
this.handleInfoMessage = this.handleInfoMessage.bind(this);
|
||||||
this.handleDataMessage = this.handleDataMessage.bind(this);
|
this.handleDataMessage = this.handleDataMessage.bind(this);
|
||||||
this.handleCallClose = this.handleCallClose.bind(this);
|
this.handleCallClose = this.handleCallClose.bind(this);
|
||||||
@@ -233,6 +239,7 @@ class TinodeWeb extends React.Component {
|
|||||||
// Video calls.
|
// Video calls.
|
||||||
callTopic: undefined,
|
callTopic: undefined,
|
||||||
callState: CALL_STATE_NONE,
|
callState: CALL_STATE_NONE,
|
||||||
|
callAudioOnly: undefined,
|
||||||
// If true, call state should be transitioned to CALL_STATE_IN_PROGRESS upon
|
// If true, call state should be transitioned to CALL_STATE_IN_PROGRESS upon
|
||||||
// switching to the call topic.
|
// switching to the call topic.
|
||||||
callShouldStart: false,
|
callShouldStart: false,
|
||||||
@@ -717,20 +724,23 @@ class TinodeWeb extends React.Component {
|
|||||||
this.handleLoginSuccessful();
|
this.handleLoginSuccessful();
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
|
const autoLogin = err.code >= 500;
|
||||||
// Connection or login failed, report error.
|
// Connection or login failed, report error.
|
||||||
this.setState({
|
this.setState({
|
||||||
loginDisabled: false,
|
loginDisabled: false,
|
||||||
credMethod: undefined,
|
credMethod: undefined,
|
||||||
credCode: undefined,
|
credCode: undefined,
|
||||||
loadSpinnerVisible: false,
|
loadSpinnerVisible: false,
|
||||||
autoLogin: false
|
autoLogin: autoLogin
|
||||||
});
|
});
|
||||||
this.handleError(err.message, 'err');
|
this.handleError(err.message, 'err');
|
||||||
console.warn("Login failed", err);
|
console.warn("Login failed", err);
|
||||||
if (token) {
|
if (!autoLogin) {
|
||||||
this.handleLogout();
|
if (token) {
|
||||||
|
this.handleLogout();
|
||||||
|
}
|
||||||
|
HashNavigation.navigateTo('');
|
||||||
}
|
}
|
||||||
HashNavigation.navigateTo('');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1589,6 +1599,10 @@ class TinodeWeb extends React.Component {
|
|||||||
title: this.props.intl.formatMessage(messages.menu_item_info),
|
title: this.props.intl.formatMessage(messages.menu_item_info),
|
||||||
handler: this.handleShowInfoView
|
handler: this.handleShowInfoView
|
||||||
} : null,
|
} : null,
|
||||||
|
subscribed && Tinode.isP2PTopicName(topicName) && webrtc ? {
|
||||||
|
title: this.props.intl.formatMessage(messages.menu_item_audio_call),
|
||||||
|
handler: this.handleStartAudioCall
|
||||||
|
} : null,
|
||||||
subscribed && Tinode.isP2PTopicName(topicName) && webrtc ? {
|
subscribed && Tinode.isP2PTopicName(topicName) && webrtc ? {
|
||||||
title: this.props.intl.formatMessage(messages.menu_item_video_call),
|
title: this.props.intl.formatMessage(messages.menu_item_video_call),
|
||||||
handler: this.handleStartVideoCall
|
handler: this.handleStartVideoCall
|
||||||
@@ -1714,15 +1728,24 @@ class TinodeWeb extends React.Component {
|
|||||||
handleStartVideoCall() {
|
handleStartVideoCall() {
|
||||||
this.setState({
|
this.setState({
|
||||||
callTopic: this.state.topicSelected,
|
callTopic: this.state.topicSelected,
|
||||||
callState: CALL_STATE_OUTGOING_INITATED
|
callState: CALL_STATE_OUTGOING_INITATED,
|
||||||
|
callAudioOnly: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCallInvite(callTopic, callSeq, callState) {
|
handleStartAudioCall() {
|
||||||
|
this.setState({
|
||||||
|
callTopic: this.state.topicSelected,
|
||||||
|
callState: CALL_STATE_OUTGOING_INITATED,
|
||||||
|
callAudioOnly: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCallInvite(callTopic, callSeq, callState, audioOnly) {
|
||||||
switch (callState) {
|
switch (callState) {
|
||||||
case CALL_STATE_OUTGOING_INITATED:
|
case CALL_STATE_OUTGOING_INITATED:
|
||||||
let head = { webrtc: CALL_HEAD_STARTED };
|
const head = { webrtc: CALL_HEAD_STARTED, aonly: !!audioOnly };
|
||||||
this.handleSendMessage(Drafty.videoCall(), undefined, undefined, head)
|
this.handleSendMessage(Drafty.videoCall(audioOnly), undefined, undefined, head)
|
||||||
.then(ctrl => {
|
.then(ctrl => {
|
||||||
if (ctrl.code < 200 || ctrl.code >= 300 || !ctrl.params || !ctrl.params.seq) {
|
if (ctrl.code < 200 || ctrl.code >= 300 || !ctrl.params || !ctrl.params.seq) {
|
||||||
this.handleCallClose();
|
this.handleCallClose();
|
||||||
@@ -1792,7 +1815,8 @@ class TinodeWeb extends React.Component {
|
|||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
callTopic: undefined,
|
callTopic: undefined,
|
||||||
callState: CALL_STATE_NONE
|
callState: CALL_STATE_NONE,
|
||||||
|
callAudioOnly: undefined
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1825,7 +1849,8 @@ class TinodeWeb extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
callTopic: null,
|
callTopic: null,
|
||||||
callState: CALL_STATE_NONE,
|
callState: CALL_STATE_NONE,
|
||||||
callSeq: null
|
callSeq: null,
|
||||||
|
callAudioOnly: undefined
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1858,7 +1883,8 @@ class TinodeWeb extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
callTopic: data.topic,
|
callTopic: data.topic,
|
||||||
callState: CALL_STATE_INCOMING_RECEIVED,
|
callState: CALL_STATE_INCOMING_RECEIVED,
|
||||||
callSeq: data.seq
|
callSeq: data.seq,
|
||||||
|
callAudioOnly: !!msg.head.aonly
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Another call is either in progress or being established.
|
// Another call is either in progress or being established.
|
||||||
@@ -1914,10 +1940,11 @@ class TinodeWeb extends React.Component {
|
|||||||
{this.state.callTopic && this.state.callState == CALL_STATE_INCOMING_RECEIVED ?
|
{this.state.callTopic && this.state.callState == CALL_STATE_INCOMING_RECEIVED ?
|
||||||
<CallIncoming
|
<CallIncoming
|
||||||
tinode={this.tinode}
|
tinode={this.tinode}
|
||||||
onClose={this.handleCallClose}
|
|
||||||
topic={this.state.callTopic}
|
topic={this.state.callTopic}
|
||||||
seq={this.state.callSeq}
|
seq={this.state.callSeq}
|
||||||
callState={this.state.callState}
|
callState={this.state.callState}
|
||||||
|
audioOnly={this.state.callAudioOnly}
|
||||||
|
onClose={this.handleCallClose}
|
||||||
onRinging={this.handleCallRinging}
|
onRinging={this.handleCallRinging}
|
||||||
onAcceptCall={this.handleCallAccept}
|
onAcceptCall={this.handleCallAccept}
|
||||||
onReject={this.handleCallHangup}
|
onReject={this.handleCallHangup}
|
||||||
@@ -2030,6 +2057,7 @@ class TinodeWeb extends React.Component {
|
|||||||
callTopic={this.state.callTopic}
|
callTopic={this.state.callTopic}
|
||||||
callSeq={this.state.callSeq}
|
callSeq={this.state.callSeq}
|
||||||
callState={this.state.callState}
|
callState={this.state.callState}
|
||||||
|
callAudioOnly={this.state.callAudioOnly}
|
||||||
onCallHangup={this.handleCallHangup}
|
onCallHangup={this.handleCallHangup}
|
||||||
|
|
||||||
onCallInvite={this.handleCallInvite}
|
onCallInvite={this.handleCallInvite}
|
||||||
|
|||||||
+206
-39
@@ -15,6 +15,9 @@ const CALL_ENDED_SOUND = new Audio('audio/call-end.m4a');
|
|||||||
CALL_ENDED_SOUND.loop = true;
|
CALL_ENDED_SOUND.loop = true;
|
||||||
const DIALING_SOUND = new Audio('audio/dialing.m4a');
|
const DIALING_SOUND = new Audio('audio/dialing.m4a');
|
||||||
|
|
||||||
|
const VIDEO_MUTED_EVENT = 'video:muted';
|
||||||
|
const VIDEO_UNMUTED_EVENT = 'video:unmuted';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
already_in_call: {
|
already_in_call: {
|
||||||
id: 'already_in_call',
|
id: 'already_in_call',
|
||||||
@@ -30,16 +33,22 @@ class CallPanel extends React.PureComponent {
|
|||||||
this.state = {
|
this.state = {
|
||||||
localStream: undefined,
|
localStream: undefined,
|
||||||
pc: undefined,
|
pc: undefined,
|
||||||
|
dataChannel: undefined,
|
||||||
|
|
||||||
previousOnInfo: undefined,
|
previousOnInfo: undefined,
|
||||||
waitingForPeer: false,
|
waitingForPeer: false,
|
||||||
// If true, the client has received a remote SDP from the peer and has sent a local SDP to the peer.
|
// If true, the client has received a remote SDP from the peer and has sent a local SDP to the peer.
|
||||||
callInitialSetupComplete: false
|
callInitialSetupComplete: false,
|
||||||
|
audioOnly: props.callAudioOnly,
|
||||||
|
// Video mute/unmute in progress.
|
||||||
|
videoToggleInProgress: false,
|
||||||
|
// Indicates if the remote peer has informed us that their camera is on.
|
||||||
|
remoteVideoLive: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.localStreamConstraints = {
|
this.localStreamConstraints = {
|
||||||
audio: true,
|
audio: true,
|
||||||
video: true
|
video: !props.callAudioOnly
|
||||||
};
|
};
|
||||||
this.isOutgoingCall = props.callState == CALL_STATE_OUTGOING_INITATED;
|
this.isOutgoingCall = props.callState == CALL_STATE_OUTGOING_INITATED;
|
||||||
|
|
||||||
@@ -77,10 +86,19 @@ class CallPanel extends React.PureComponent {
|
|||||||
this.handleCloseClick = this.handleCloseClick.bind(this);
|
this.handleCloseClick = this.handleCloseClick.bind(this);
|
||||||
this.handleToggleCameraClick = this.handleToggleCameraClick.bind(this);
|
this.handleToggleCameraClick = this.handleToggleCameraClick.bind(this);
|
||||||
this.handleToggleMicClick = this.handleToggleMicClick.bind(this);
|
this.handleToggleMicClick = this.handleToggleMicClick.bind(this);
|
||||||
this.toggleMedia = this.toggleMedia.bind(this);
|
|
||||||
|
|
||||||
this.handleRemoteHangup = this.handleRemoteHangup.bind(this);
|
this.handleRemoteHangup = this.handleRemoteHangup.bind(this);
|
||||||
this.handleVideoCallAccepted = this.handleVideoCallAccepted.bind(this);
|
this.handleVideoCallAccepted = this.handleVideoCallAccepted.bind(this);
|
||||||
|
|
||||||
|
this.muteVideo = this.muteVideo.bind(this);
|
||||||
|
this.unmuteVideo = this.unmuteVideo.bind(this);
|
||||||
|
this.emptyVideoTrack = this.emptyVideoTrack.bind(this);
|
||||||
|
|
||||||
|
this.handleDataChannelEvent = this.handleDataChannelEvent.bind(this);
|
||||||
|
this.handleDataChannelError = this.handleDataChannelError.bind(this);
|
||||||
|
this.handleDataChannelMessage = this.handleDataChannelMessage.bind(this);
|
||||||
|
this.handleDataChannelOpen = this.handleDataChannelOpen.bind(this);
|
||||||
|
this.handleDataChannelClose = this.handleDataChannelClose.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -105,6 +123,13 @@ class CallPanel extends React.PureComponent {
|
|||||||
const stream = this.state.localStream;
|
const stream = this.state.localStream;
|
||||||
stream.getTracks().forEach(track => {
|
stream.getTracks().forEach(track => {
|
||||||
pc.addTrack(track, stream);
|
pc.addTrack(track, stream);
|
||||||
|
|
||||||
|
if (track.kind == 'video' && !this.localStreamConstraints.video) {
|
||||||
|
// This is an audio-only call.
|
||||||
|
// Remove dummy video track (placeholder remains).
|
||||||
|
track.stop();
|
||||||
|
stream.removeTrack(track);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +163,16 @@ class CallPanel extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates an empty video track placeholder.
|
||||||
|
emptyVideoTrack() {
|
||||||
|
const width = 640;
|
||||||
|
const height = 480;
|
||||||
|
const canvas = Object.assign(document.createElement("canvas"), {width, height});
|
||||||
|
canvas.getContext('2d').fillRect(0, 0, width, height);
|
||||||
|
const stream = canvas.captureStream(0);
|
||||||
|
return Object.assign(stream.getVideoTracks()[0], {enabled: false});
|
||||||
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if (this.state.localStream) {
|
if (this.state.localStream) {
|
||||||
this.props.onError(this.props.intl.formatMessage(messages.already_in_call), 'info');
|
this.props.onError(this.props.intl.formatMessage(messages.already_in_call), 'info');
|
||||||
@@ -146,7 +181,7 @@ class CallPanel extends React.PureComponent {
|
|||||||
|
|
||||||
if (this.props.callState == CALL_STATE_IN_PROGRESS) {
|
if (this.props.callState == CALL_STATE_IN_PROGRESS) {
|
||||||
// We apparently just accepted the call.
|
// We apparently just accepted the call.
|
||||||
this.props.onInvite(this.props.topic, this.props.seq, this.props.callState);
|
this.props.onInvite(this.props.topic, this.props.seq, CALL_STATE_IN_PROGRESS, this.props.callAudioOnly);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,13 +189,18 @@ class CallPanel extends React.PureComponent {
|
|||||||
// Start local video.
|
// Start local video.
|
||||||
navigator.mediaDevices.getUserMedia(this.localStreamConstraints)
|
navigator.mediaDevices.getUserMedia(this.localStreamConstraints)
|
||||||
.then(stream => {
|
.then(stream => {
|
||||||
|
if (!this.localStreamConstraints.video) {
|
||||||
|
// Starting an audio-only call. Create a dummy video track
|
||||||
|
// (so video can be enabled during the call if the user desires).
|
||||||
|
stream.addTrack(this.emptyVideoTrack());
|
||||||
|
}
|
||||||
this.setState({localStream: stream, waitingForPeer: true});
|
this.setState({localStream: stream, waitingForPeer: true});
|
||||||
this.localRef.current.srcObject = stream;
|
this.localRef.current.srcObject = stream;
|
||||||
|
|
||||||
DIALING_SOUND.play();
|
DIALING_SOUND.play();
|
||||||
|
|
||||||
// Send call invitation.
|
// Send call invitation.
|
||||||
this.props.onInvite(this.props.topic, this.props.seq, this.props.callState);
|
this.props.onInvite(this.props.topic, this.props.seq, this.props.callState, this.props.callAudioOnly);
|
||||||
})
|
})
|
||||||
.catch(this.handleGetUserMediaError);
|
.catch(this.handleGetUserMediaError);
|
||||||
}
|
}
|
||||||
@@ -183,7 +223,11 @@ class CallPanel extends React.PureComponent {
|
|||||||
this.state.pc.onicegatheringstatechange = null;
|
this.state.pc.onicegatheringstatechange = null;
|
||||||
this.state.pc.onnegotiationneeded = null;
|
this.state.pc.onnegotiationneeded = null;
|
||||||
this.state.pc.onicecandidateerror = null;
|
this.state.pc.onicecandidateerror = null;
|
||||||
|
this.state.pc.ondatachannel = null;
|
||||||
|
|
||||||
|
if (this.state.dataChannel) {
|
||||||
|
this.state.dataChannel.close();
|
||||||
|
}
|
||||||
this.state.pc.close();
|
this.state.pc.close();
|
||||||
}
|
}
|
||||||
this.setState({pc: null, waitingForPeer: false});
|
this.setState({pc: null, waitingForPeer: false});
|
||||||
@@ -209,6 +253,42 @@ class CallPanel extends React.PureComponent {
|
|||||||
el.src = '';
|
el.src = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDataChannelError(error) {
|
||||||
|
console.error('data channel error', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDataChannelMessage(event) {
|
||||||
|
switch (event.data) {
|
||||||
|
case VIDEO_MUTED_EVENT:
|
||||||
|
this.setState({remoteVideoLive: false});
|
||||||
|
break;
|
||||||
|
case VIDEO_UNMUTED_EVENT:
|
||||||
|
this.setState({remoteVideoLive: true});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDataChannelOpen(event) {
|
||||||
|
if (!this.state.audioOnly) {
|
||||||
|
event.target.send(VIDEO_UNMUTED_EVENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDataChannelClose() {
|
||||||
|
/* no action */
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDataChannelEvent(event) {
|
||||||
|
const channel = event.channel;
|
||||||
|
channel.onerror = this.handleDataChannelError;
|
||||||
|
channel.onmessage = this.handleDataChannelMessage;
|
||||||
|
channel.onopen = this.handleDataChannelOpen;
|
||||||
|
channel.onclose = this.handleDataChannelClose;
|
||||||
|
this.setState({dataChannel: channel});
|
||||||
|
}
|
||||||
|
|
||||||
createPeerConnection() {
|
createPeerConnection() {
|
||||||
const iceServers = this.props.tinode.getServerParam('iceServers', null);
|
const iceServers = this.props.tinode.getServerParam('iceServers', null);
|
||||||
const pc = iceServers ? new RTCPeerConnection({iceServers: iceServers}) : new RTCPeerConnection();
|
const pc = iceServers ? new RTCPeerConnection({iceServers: iceServers}) : new RTCPeerConnection();
|
||||||
@@ -220,8 +300,17 @@ class CallPanel extends React.PureComponent {
|
|||||||
pc.onnegotiationneeded = this.handleNegotiationNeededEvent;
|
pc.onnegotiationneeded = this.handleNegotiationNeededEvent;
|
||||||
pc.onicecandidateerror = this.handleIceCandidateErrorEvent;
|
pc.onicecandidateerror = this.handleIceCandidateErrorEvent;
|
||||||
pc.ontrack = this.handleTrackEvent;
|
pc.ontrack = this.handleTrackEvent;
|
||||||
|
pc.ondatachannel = this.handleDataChannelEvent;
|
||||||
|
|
||||||
|
// creating data channel events
|
||||||
|
const channel = pc.createDataChannel("events", {ordered: true});
|
||||||
|
channel.onerror = this.handleDataChannelError;
|
||||||
|
channel.onmessage = this.handleDataChannelMessage;
|
||||||
|
channel.onopen = this.handleDataChannelOpen;
|
||||||
|
channel.onclose = this.handleDataChannelClose;
|
||||||
|
|
||||||
this.setState({pc: pc, waitingForPeer: false});
|
this.setState({pc: pc, waitingForPeer: false});
|
||||||
|
|
||||||
return pc;
|
return pc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +322,7 @@ class CallPanel extends React.PureComponent {
|
|||||||
.then(_ => {
|
.then(_ => {
|
||||||
this.setState({ callInitialSetupComplete: true }, _ => this.drainRemoteIceCandidatesCache());
|
this.setState({ callInitialSetupComplete: true }, _ => this.drainRemoteIceCandidatesCache());
|
||||||
})
|
})
|
||||||
.catch(this.reportError);
|
.catch(err => this.reportError(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
reportError(err) {
|
reportError(err) {
|
||||||
@@ -244,17 +333,18 @@ class CallPanel extends React.PureComponent {
|
|||||||
return this.isOutgoingCall || this.state.callInitialSetupComplete;
|
return this.isOutgoingCall || this.state.callInitialSetupComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNegotiationNeededEvent() {
|
handleNegotiationNeededEvent(event) {
|
||||||
|
const pc = event.target;
|
||||||
if (!this.canSendOffer()) {
|
if (!this.canSendOffer()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.state.pc.createOffer().then(offer => {
|
pc.createOffer().then(offer => {
|
||||||
return this.state.pc.setLocalDescription(offer);
|
return pc.setLocalDescription(offer);
|
||||||
})
|
})
|
||||||
.then(_ => {
|
.then(_ => {
|
||||||
this.props.onSendOffer(this.props.topic, this.props.seq, this.state.pc.localDescription.toJSON());
|
this.props.onSendOffer(this.props.topic, this.props.seq, pc.localDescription.toJSON());
|
||||||
})
|
})
|
||||||
.catch(this.reportError);
|
.catch(err => this.reportError(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIceCandidateErrorEvent(event) {
|
handleIceCandidateErrorEvent(event) {
|
||||||
@@ -271,7 +361,12 @@ class CallPanel extends React.PureComponent {
|
|||||||
const candidate = new RTCIceCandidate(info.payload);
|
const candidate = new RTCIceCandidate(info.payload);
|
||||||
if (this.state.callInitialSetupComplete) {
|
if (this.state.callInitialSetupComplete) {
|
||||||
this.state.pc.addIceCandidate(candidate)
|
this.state.pc.addIceCandidate(candidate)
|
||||||
.catch(this.reportError);
|
.catch(err => {
|
||||||
|
if (candidate.candidate) {
|
||||||
|
this.reportError(err);
|
||||||
|
}
|
||||||
|
console.warn("Error adding new ice candidate", candidate, err);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.remoteIceCandidatesCache.push(candidate);
|
this.remoteIceCandidatesCache.push(candidate);
|
||||||
}
|
}
|
||||||
@@ -280,7 +375,12 @@ class CallPanel extends React.PureComponent {
|
|||||||
drainRemoteIceCandidatesCache() {
|
drainRemoteIceCandidatesCache() {
|
||||||
this.remoteIceCandidatesCache.forEach(candidate => {
|
this.remoteIceCandidatesCache.forEach(candidate => {
|
||||||
this.state.pc.addIceCandidate(candidate)
|
this.state.pc.addIceCandidate(candidate)
|
||||||
.catch(this.reportError);
|
.catch(err => {
|
||||||
|
if (candidate.candidate) {
|
||||||
|
this.reportError(err);
|
||||||
|
}
|
||||||
|
console.warn("Error adding cached ice candidate", candidate, err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
this.remoteIceCandidatesCache = [];
|
this.remoteIceCandidatesCache = [];
|
||||||
}
|
}
|
||||||
@@ -295,7 +395,7 @@ class CallPanel extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSignalingStateChangeEvent(event) {
|
handleSignalingStateChangeEvent(event) {
|
||||||
if (this.state.pc.signalingState == 'closed') {
|
if (event.target.signalingState == 'closed') {
|
||||||
this.handleCloseClick();
|
this.handleCloseClick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,23 +407,24 @@ class CallPanel extends React.PureComponent {
|
|||||||
handleTrackEvent(event) {
|
handleTrackEvent(event) {
|
||||||
// Remote video becomes available.
|
// Remote video becomes available.
|
||||||
this.remoteRef.current.srcObject = event.streams[0];
|
this.remoteRef.current.srcObject = event.streams[0];
|
||||||
|
|
||||||
// Make sure we display the title (peer's name) over the remote video.
|
// Make sure we display the title (peer's name) over the remote video.
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleGetUserMediaError(e) {
|
handleGetUserMediaError(e) {
|
||||||
|
console.error("Error opening camera and/or microphone", e);
|
||||||
switch(e.name) {
|
switch(e.name) {
|
||||||
case 'NotFoundError':
|
case 'NotFoundError':
|
||||||
// Cannot start the call b/c no camera and/or microphone found.
|
// Cannot start the call b/c no camera and/or microphone found.
|
||||||
this.reportError(e.message);
|
this.reportError(e);
|
||||||
break;
|
break;
|
||||||
case 'SecurityError':
|
case 'SecurityError':
|
||||||
case 'PermissionDeniedError':
|
case 'PermissionDeniedError':
|
||||||
// Do nothing; this is the same as the user canceling the call.
|
// Do nothing; this is the same as the user canceling the call.
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.reportError(e.message);
|
this.reportError(e);
|
||||||
console.error("Error opening your camera and/or microphone:", e.message);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,13 +435,20 @@ class CallPanel extends React.PureComponent {
|
|||||||
|
|
||||||
handleVideoOfferMsg(info) {
|
handleVideoOfferMsg(info) {
|
||||||
let localStream = null;
|
let localStream = null;
|
||||||
const pc = this.createPeerConnection();
|
const pc = this.state.pc ? this.state.pc : this.createPeerConnection();
|
||||||
const desc = new RTCSessionDescription(info.payload);
|
const desc = new RTCSessionDescription(info.payload);
|
||||||
|
|
||||||
pc.setRemoteDescription(desc).then(_ => {
|
pc.setRemoteDescription(desc).then(_ => {
|
||||||
return navigator.mediaDevices.getUserMedia(this.localStreamConstraints);
|
return navigator.mediaDevices.getUserMedia(this.localStreamConstraints);
|
||||||
})
|
})
|
||||||
.then(stream => {
|
.then(stream => {
|
||||||
|
let dummyVideo;
|
||||||
|
if (!this.localStreamConstraints.video) {
|
||||||
|
// Starting an audio-only call. Create an empty video track so
|
||||||
|
// so the user can enable the video during the call.
|
||||||
|
dummyVideo = this.emptyVideoTrack();
|
||||||
|
stream.addTrack(dummyVideo);
|
||||||
|
}
|
||||||
localStream = stream;
|
localStream = stream;
|
||||||
this.localRef.current.srcObject = stream;
|
this.localRef.current.srcObject = stream;
|
||||||
this.setState({localStream: stream});
|
this.setState({localStream: stream});
|
||||||
@@ -348,6 +456,11 @@ class CallPanel extends React.PureComponent {
|
|||||||
localStream.getTracks().forEach(track => {
|
localStream.getTracks().forEach(track => {
|
||||||
pc.addTrack(track, localStream);
|
pc.addTrack(track, localStream);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (dummyVideo) {
|
||||||
|
dummyVideo.stop();
|
||||||
|
stream.removeTrack(dummyVideo);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(_ => {
|
.then(_ => {
|
||||||
return pc.createAnswer();
|
return pc.createAnswer();
|
||||||
@@ -387,49 +500,103 @@ class CallPanel extends React.PureComponent {
|
|||||||
this.props.onHangup(this.props.topic, this.props.seq);
|
this.props.onHangup(this.props.topic, this.props.seq);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mute/unmute audio/video (specified by kind).
|
// Ends video track and turns off the camera.
|
||||||
toggleMedia(kind) {
|
muteVideo() {
|
||||||
|
if (!this.state.pc || !this.state.dataChannel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const stream = this.state.localStream;
|
const stream = this.state.localStream;
|
||||||
stream.getTracks().forEach(track => {
|
const t = stream.getVideoTracks()[0];
|
||||||
if (track.kind != kind) {
|
t.enabled = false;
|
||||||
return;
|
t.stop();
|
||||||
|
|
||||||
|
stream.removeTrack(t);
|
||||||
|
this.state.dataChannel.send(VIDEO_MUTED_EVENT);
|
||||||
|
this.setState({videoToggleInProgress: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
unmuteVideo() {
|
||||||
|
if (!this.state.pc || !this.state.dataChannel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.mediaDevices.getUserMedia({ video: true })
|
||||||
|
.then(stream => {
|
||||||
|
// Will extract video track from stream and throw stream away,
|
||||||
|
// and replace video track in the media sender.
|
||||||
|
this.localRef.current.srcObject = null;
|
||||||
|
const sender = this.state.pc.getSenders().find(s => s.track.kind == 'video');
|
||||||
|
const track = stream.getVideoTracks()[0];
|
||||||
|
// Remote track from new stream.
|
||||||
|
stream.removeTrack(track);
|
||||||
|
// Add this track to the existing local stream.
|
||||||
|
this.state.localStream.addTrack(track);
|
||||||
|
return sender.replaceTrack(track);
|
||||||
|
})
|
||||||
|
.then(_ => {
|
||||||
|
this.localRef.current.srcObject = this.state.localStream;
|
||||||
|
this.state.dataChannel.send(VIDEO_UNMUTED_EVENT);
|
||||||
|
})
|
||||||
|
.catch(err => this.handleGetUserMediaError(err))
|
||||||
|
.finally(_ => { this.setState({videoToggleInProgress: false}); }); // Make sure we redraw the mute/unmute icons (e.g. camera -> camera_off).
|
||||||
|
}
|
||||||
|
|
||||||
|
handleToggleCameraClick() {
|
||||||
|
if (this.state.videoToggleInProgress) {
|
||||||
|
// Toggle currently in progress.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tracks = this.state.localStream.getVideoTracks();
|
||||||
|
this.setState({videoToggleInProgress: true}, _ => {
|
||||||
|
if (tracks && tracks.length > 0 && tracks[0].enabled && tracks[0].readyState == 'live') {
|
||||||
|
this.muteVideo();
|
||||||
|
} else {
|
||||||
|
this.unmuteVideo();
|
||||||
}
|
}
|
||||||
track.enabled = !track.enabled;
|
this.setState({audioOnly: !this.state.audioOnly});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleToggleMicClick() {
|
||||||
|
const stream = this.state.localStream;
|
||||||
|
const t = stream.getAudioTracks()[0];
|
||||||
|
t.enabled = !t.enabled;
|
||||||
// Make sure we redraw the mute/unmute icons (e.g. mic -> mic_off).
|
// Make sure we redraw the mute/unmute icons (e.g. mic -> mic_off).
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleToggleCameraClick() {
|
|
||||||
this.toggleMedia('video');
|
|
||||||
}
|
|
||||||
|
|
||||||
handleToggleMicClick() {
|
|
||||||
this.toggleMedia('audio');
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const remoteActive = this.remoteRef.current && this.remoteRef.current.srcObject;
|
const audioTracks = this.state.localStream && this.state.localStream.getAudioTracks();
|
||||||
const disabled = !(this.state.localStream && this.state.localStream.getTracks());
|
const videoTracks = !this.state.audioOnly && this.state.localStream && this.state.localStream.getVideoTracks();
|
||||||
const audioIcon = this.state.localStream && this.state.localStream.getAudioTracks()[0].enabled ? 'mic' : 'mic_off';
|
const disabled = !this.state.pc || !this.state.dataChannel || !(audioTracks && audioTracks[0]);
|
||||||
const videoIcon = this.state.localStream && this.state.localStream.getVideoTracks()[0].enabled ? 'videocam' : 'videocam_off';
|
const audioIcon = audioTracks && audioTracks[0] && audioTracks[0].enabled ? 'mic' : 'mic_off';
|
||||||
|
const videoIcon = videoTracks && videoTracks[0] && videoTracks[0].enabled && videoTracks[0].readyState == 'live' ? 'videocam' : 'videocam_off';
|
||||||
const peerTitle = clipStr(this.props.title, MAX_PEER_TITLE_LENGTH);
|
const peerTitle = clipStr(this.props.title, MAX_PEER_TITLE_LENGTH);
|
||||||
const pulseAnimation = this.state.waitingForPeer ? ' pulse' : '';
|
const pulseAnimation = this.state.waitingForPeer ? ' pulse' : '';
|
||||||
|
|
||||||
|
let removeActive = false;
|
||||||
|
if (this.remoteRef.current && this.remoteRef.current.srcObject && this.state.remoteVideoLive) {
|
||||||
|
const rstream = this.remoteRef.current.srcObject;
|
||||||
|
if (rstream.getVideoTracks().length > 0) {
|
||||||
|
const t = rstream.getVideoTracks()[0];
|
||||||
|
removeActive = t.enabled && t.readyState == 'live';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div id="video-container">
|
<div id="video-container">
|
||||||
<div id="video-container-panel">
|
<div id="video-container-panel">
|
||||||
<div className="call-party self">
|
<div className="call-party self" disabled={this.state.audioOnly}>
|
||||||
<video ref={this.localRef} autoPlay muted playsInline />
|
<video ref={this.localRef} autoPlay muted playsInline />
|
||||||
<div className="caller-name inactive">
|
<div className="caller-name inactive">
|
||||||
<FormattedMessage id="calls_you_label"
|
<FormattedMessage id="calls_you_label"
|
||||||
defaultMessage="You" description="Shown over the local video screen" />
|
defaultMessage="You" description="Shown over the local video screen" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="call-party peer">
|
<div className="call-party peer" disabled={!removeActive}>
|
||||||
<video ref={this.remoteRef} autoPlay playsInline />
|
<video ref={this.remoteRef} autoPlay playsInline />
|
||||||
{remoteActive ?
|
{removeActive ?
|
||||||
<div className="caller-name inactive">{peerTitle}</div> :
|
<div className="caller-name inactive">{peerTitle}</div> :
|
||||||
<div className={`caller-card${pulseAnimation}`}>
|
<div className={`caller-card${pulseAnimation}`}>
|
||||||
<div className="avatar-box">
|
<div className="avatar-box">
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default class CallStatus extends React.PureComponent {
|
|||||||
duration = <FormattedMessage id="call_disconnected" defaultMessage="disconnected" description="Label for disconnected call" />;
|
duration = <FormattedMessage id="call_disconnected" defaultMessage="disconnected" description="Label for disconnected call" />;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (this.props.callState == 'started' && !this.props.duration) {
|
} else if (['accepted', 'started'].includes(this.props.callState) && !this.props.duration) {
|
||||||
duration = <FormattedMessage id="call_in_progress" defaultMessage="in progress" description="Label for call in progress" />
|
duration = <FormattedMessage id="call_in_progress" defaultMessage="in progress" description="Label for call in progress" />
|
||||||
} else {
|
} else {
|
||||||
duration = <span>{secondsToTime(this.props.duration / 1000)}</span>;
|
duration = <span>{secondsToTime(this.props.duration / 1000)}</span>;
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+236
-51
@@ -3544,7 +3544,7 @@ __webpack_require__.r(__webpack_exports__);
|
|||||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||||||
/* harmony export */ "PACKAGE_VERSION": () => (/* binding */ PACKAGE_VERSION)
|
/* harmony export */ "PACKAGE_VERSION": () => (/* binding */ PACKAGE_VERSION)
|
||||||
/* harmony export */ });
|
/* harmony export */ });
|
||||||
const PACKAGE_VERSION = "0.21.0-beta1";
|
const PACKAGE_VERSION = "0.21.0-beta2";
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
@@ -5721,8 +5721,6 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
|||||||
scrollPosition: 0,
|
scrollPosition: 0,
|
||||||
fetchingMessages: false,
|
fetchingMessages: false,
|
||||||
showGoToLastButton: false,
|
showGoToLastButton: false,
|
||||||
forwardMessage: null,
|
|
||||||
reply: null,
|
|
||||||
contentToEdit: null,
|
contentToEdit: null,
|
||||||
dragging: false
|
dragging: false
|
||||||
};
|
};
|
||||||
@@ -5731,6 +5729,8 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
|||||||
content: nextProps.forwardMessage.preview,
|
content: nextProps.forwardMessage.preview,
|
||||||
seq: null
|
seq: null
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
nextState.reply = null;
|
||||||
}
|
}
|
||||||
if (topic) {
|
if (topic) {
|
||||||
const subs = [];
|
const subs = [];
|
||||||
@@ -6635,6 +6635,7 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
|
|||||||
topic: this.state.topic,
|
topic: this.state.topic,
|
||||||
seq: this.props.callSeq,
|
seq: this.props.callSeq,
|
||||||
callState: this.props.callState,
|
callState: this.props.callState,
|
||||||
|
callAudioOnly: this.props.callAudioOnly,
|
||||||
tinode: this.props.tinode,
|
tinode: this.props.tinode,
|
||||||
title: this.state.title,
|
title: this.state.title,
|
||||||
avatar: this.state.avatar || true,
|
avatar: this.state.avatar || true,
|
||||||
@@ -7772,6 +7773,13 @@ const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
|
|||||||
"value": "Info"
|
"value": "Info"
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
menu_item_audio_call: {
|
||||||
|
id: "menu_item_audio_call",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Call"
|
||||||
|
}]
|
||||||
|
},
|
||||||
menu_item_video_call: {
|
menu_item_video_call: {
|
||||||
id: "menu_item_video_call",
|
id: "menu_item_video_call",
|
||||||
defaultMessage: [{
|
defaultMessage: [{
|
||||||
@@ -7859,6 +7867,7 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
this.handleShowForwardDialog = this.handleShowForwardDialog.bind(this);
|
this.handleShowForwardDialog = this.handleShowForwardDialog.bind(this);
|
||||||
this.handleHideForwardDialog = this.handleHideForwardDialog.bind(this);
|
this.handleHideForwardDialog = this.handleHideForwardDialog.bind(this);
|
||||||
this.handleStartVideoCall = this.handleStartVideoCall.bind(this);
|
this.handleStartVideoCall = this.handleStartVideoCall.bind(this);
|
||||||
|
this.handleStartAudioCall = this.handleStartAudioCall.bind(this);
|
||||||
this.handleInfoMessage = this.handleInfoMessage.bind(this);
|
this.handleInfoMessage = this.handleInfoMessage.bind(this);
|
||||||
this.handleDataMessage = this.handleDataMessage.bind(this);
|
this.handleDataMessage = this.handleDataMessage.bind(this);
|
||||||
this.handleCallClose = this.handleCallClose.bind(this);
|
this.handleCallClose = this.handleCallClose.bind(this);
|
||||||
@@ -7913,6 +7922,7 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
mobilePanel: 'sidepanel',
|
mobilePanel: 'sidepanel',
|
||||||
callTopic: undefined,
|
callTopic: undefined,
|
||||||
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_NONE,
|
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_NONE,
|
||||||
|
callAudioOnly: undefined,
|
||||||
callShouldStart: false,
|
callShouldStart: false,
|
||||||
contextMenuVisible: false,
|
contextMenuVisible: false,
|
||||||
contextMenuBounds: null,
|
contextMenuBounds: null,
|
||||||
@@ -8347,19 +8357,22 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
this.handleLoginSuccessful();
|
this.handleLoginSuccessful();
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
|
const autoLogin = err.code >= 500;
|
||||||
this.setState({
|
this.setState({
|
||||||
loginDisabled: false,
|
loginDisabled: false,
|
||||||
credMethod: undefined,
|
credMethod: undefined,
|
||||||
credCode: undefined,
|
credCode: undefined,
|
||||||
loadSpinnerVisible: false,
|
loadSpinnerVisible: false,
|
||||||
autoLogin: false
|
autoLogin: autoLogin
|
||||||
});
|
});
|
||||||
this.handleError(err.message, 'err');
|
this.handleError(err.message, 'err');
|
||||||
console.warn("Login failed", err);
|
console.warn("Login failed", err);
|
||||||
if (token) {
|
if (!autoLogin) {
|
||||||
this.handleLogout();
|
if (token) {
|
||||||
|
this.handleLogout();
|
||||||
|
}
|
||||||
|
_lib_navigation_js__WEBPACK_IMPORTED_MODULE_18__["default"].navigateTo('');
|
||||||
}
|
}
|
||||||
_lib_navigation_js__WEBPACK_IMPORTED_MODULE_18__["default"].navigateTo('');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
static navigateToCredentialsView(params) {
|
static navigateToCredentialsView(params) {
|
||||||
@@ -9105,6 +9118,9 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
return [subscribed ? {
|
return [subscribed ? {
|
||||||
title: this.props.intl.formatMessage(messages.menu_item_info),
|
title: this.props.intl.formatMessage(messages.menu_item_info),
|
||||||
handler: this.handleShowInfoView
|
handler: this.handleShowInfoView
|
||||||
|
} : null, subscribed && tinode_sdk__WEBPACK_IMPORTED_MODULE_4__.Tinode.isP2PTopicName(topicName) && webrtc ? {
|
||||||
|
title: this.props.intl.formatMessage(messages.menu_item_audio_call),
|
||||||
|
handler: this.handleStartAudioCall
|
||||||
} : null, subscribed && tinode_sdk__WEBPACK_IMPORTED_MODULE_4__.Tinode.isP2PTopicName(topicName) && webrtc ? {
|
} : null, subscribed && tinode_sdk__WEBPACK_IMPORTED_MODULE_4__.Tinode.isP2PTopicName(topicName) && webrtc ? {
|
||||||
title: this.props.intl.formatMessage(messages.menu_item_video_call),
|
title: this.props.intl.formatMessage(messages.menu_item_video_call),
|
||||||
handler: this.handleStartVideoCall
|
handler: this.handleStartVideoCall
|
||||||
@@ -9213,16 +9229,25 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
handleStartVideoCall() {
|
handleStartVideoCall() {
|
||||||
this.setState({
|
this.setState({
|
||||||
callTopic: this.state.topicSelected,
|
callTopic: this.state.topicSelected,
|
||||||
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_OUTGOING_INITATED
|
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_OUTGOING_INITATED,
|
||||||
|
callAudioOnly: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
handleCallInvite(callTopic, callSeq, callState) {
|
handleStartAudioCall() {
|
||||||
|
this.setState({
|
||||||
|
callTopic: this.state.topicSelected,
|
||||||
|
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_OUTGOING_INITATED,
|
||||||
|
callAudioOnly: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleCallInvite(callTopic, callSeq, callState, audioOnly) {
|
||||||
switch (callState) {
|
switch (callState) {
|
||||||
case _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_OUTGOING_INITATED:
|
case _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_OUTGOING_INITATED:
|
||||||
let head = {
|
const head = {
|
||||||
webrtc: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_HEAD_STARTED
|
webrtc: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_HEAD_STARTED,
|
||||||
|
aonly: !!audioOnly
|
||||||
};
|
};
|
||||||
this.handleSendMessage(tinode_sdk__WEBPACK_IMPORTED_MODULE_4__.Drafty.videoCall(), undefined, undefined, head).then(ctrl => {
|
this.handleSendMessage(tinode_sdk__WEBPACK_IMPORTED_MODULE_4__.Drafty.videoCall(audioOnly), undefined, undefined, head).then(ctrl => {
|
||||||
if (ctrl.code < 200 || ctrl.code >= 300 || !ctrl.params || !ctrl.params.seq) {
|
if (ctrl.code < 200 || ctrl.code >= 300 || !ctrl.params || !ctrl.params.seq) {
|
||||||
this.handleCallClose();
|
this.handleCallClose();
|
||||||
return;
|
return;
|
||||||
@@ -9282,7 +9307,8 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
callTopic: undefined,
|
callTopic: undefined,
|
||||||
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_NONE
|
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_NONE,
|
||||||
|
callAudioOnly: undefined
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
handleCallAccept(topicName) {
|
handleCallAccept(topicName) {
|
||||||
@@ -9311,7 +9337,8 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
this.setState({
|
this.setState({
|
||||||
callTopic: null,
|
callTopic: null,
|
||||||
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_NONE,
|
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_NONE,
|
||||||
callSeq: null
|
callSeq: null,
|
||||||
|
callAudioOnly: undefined
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -9337,7 +9364,8 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
this.setState({
|
this.setState({
|
||||||
callTopic: data.topic,
|
callTopic: data.topic,
|
||||||
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_INCOMING_RECEIVED,
|
callState: _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_INCOMING_RECEIVED,
|
||||||
callSeq: data.seq
|
callSeq: data.seq,
|
||||||
|
callAudioOnly: !!msg.head.aonly
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.handleCallHangup(data.topic, data.seq);
|
this.handleCallHangup(data.topic, data.seq);
|
||||||
@@ -9380,10 +9408,11 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
onTopicSelected: this.handleStartTopicRequest
|
onTopicSelected: this.handleStartTopicRequest
|
||||||
}) : null, this.state.callTopic && this.state.callState == _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_INCOMING_RECEIVED ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_widgets_call_incoming_jsx__WEBPACK_IMPORTED_MODULE_8__["default"], {
|
}) : null, this.state.callTopic && this.state.callState == _constants_js__WEBPACK_IMPORTED_MODULE_13__.CALL_STATE_INCOMING_RECEIVED ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_widgets_call_incoming_jsx__WEBPACK_IMPORTED_MODULE_8__["default"], {
|
||||||
tinode: this.tinode,
|
tinode: this.tinode,
|
||||||
onClose: this.handleCallClose,
|
|
||||||
topic: this.state.callTopic,
|
topic: this.state.callTopic,
|
||||||
seq: this.state.callSeq,
|
seq: this.state.callSeq,
|
||||||
callState: this.state.callState,
|
callState: this.state.callState,
|
||||||
|
audioOnly: this.state.callAudioOnly,
|
||||||
|
onClose: this.handleCallClose,
|
||||||
onRinging: this.handleCallRinging,
|
onRinging: this.handleCallRinging,
|
||||||
onAcceptCall: this.handleCallAccept,
|
onAcceptCall: this.handleCallAccept,
|
||||||
onReject: this.handleCallHangup
|
onReject: this.handleCallHangup
|
||||||
@@ -9486,6 +9515,7 @@ class TinodeWeb extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component)
|
|||||||
callTopic: this.state.callTopic,
|
callTopic: this.state.callTopic,
|
||||||
callSeq: this.state.callSeq,
|
callSeq: this.state.callSeq,
|
||||||
callState: this.state.callState,
|
callState: this.state.callState,
|
||||||
|
callAudioOnly: this.state.callAudioOnly,
|
||||||
onCallHangup: this.handleCallHangup,
|
onCallHangup: this.handleCallHangup,
|
||||||
onCallInvite: this.handleCallInvite,
|
onCallInvite: this.handleCallInvite,
|
||||||
onCallSendOffer: this.handleCallSendOffer,
|
onCallSendOffer: this.handleCallSendOffer,
|
||||||
@@ -10662,6 +10692,8 @@ RING_SOUND.loop = true;
|
|||||||
const CALL_ENDED_SOUND = new Audio('audio/call-end.m4a');
|
const CALL_ENDED_SOUND = new Audio('audio/call-end.m4a');
|
||||||
CALL_ENDED_SOUND.loop = true;
|
CALL_ENDED_SOUND.loop = true;
|
||||||
const DIALING_SOUND = new Audio('audio/dialing.m4a');
|
const DIALING_SOUND = new Audio('audio/dialing.m4a');
|
||||||
|
const VIDEO_MUTED_EVENT = 'video:muted';
|
||||||
|
const VIDEO_UNMUTED_EVENT = 'video:unmuted';
|
||||||
const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
|
const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
|
||||||
already_in_call: {
|
already_in_call: {
|
||||||
id: "already_in_call",
|
id: "already_in_call",
|
||||||
@@ -10677,13 +10709,17 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
this.state = {
|
this.state = {
|
||||||
localStream: undefined,
|
localStream: undefined,
|
||||||
pc: undefined,
|
pc: undefined,
|
||||||
|
dataChannel: undefined,
|
||||||
previousOnInfo: undefined,
|
previousOnInfo: undefined,
|
||||||
waitingForPeer: false,
|
waitingForPeer: false,
|
||||||
callInitialSetupComplete: false
|
callInitialSetupComplete: false,
|
||||||
|
audioOnly: props.callAudioOnly,
|
||||||
|
videoToggleInProgress: false,
|
||||||
|
remoteVideoLive: false
|
||||||
};
|
};
|
||||||
this.localStreamConstraints = {
|
this.localStreamConstraints = {
|
||||||
audio: true,
|
audio: true,
|
||||||
video: true
|
video: !props.callAudioOnly
|
||||||
};
|
};
|
||||||
this.isOutgoingCall = props.callState == _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_OUTGOING_INITATED;
|
this.isOutgoingCall = props.callState == _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_OUTGOING_INITATED;
|
||||||
this.localRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef();
|
this.localRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef();
|
||||||
@@ -10712,9 +10748,16 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
this.handleCloseClick = this.handleCloseClick.bind(this);
|
this.handleCloseClick = this.handleCloseClick.bind(this);
|
||||||
this.handleToggleCameraClick = this.handleToggleCameraClick.bind(this);
|
this.handleToggleCameraClick = this.handleToggleCameraClick.bind(this);
|
||||||
this.handleToggleMicClick = this.handleToggleMicClick.bind(this);
|
this.handleToggleMicClick = this.handleToggleMicClick.bind(this);
|
||||||
this.toggleMedia = this.toggleMedia.bind(this);
|
|
||||||
this.handleRemoteHangup = this.handleRemoteHangup.bind(this);
|
this.handleRemoteHangup = this.handleRemoteHangup.bind(this);
|
||||||
this.handleVideoCallAccepted = this.handleVideoCallAccepted.bind(this);
|
this.handleVideoCallAccepted = this.handleVideoCallAccepted.bind(this);
|
||||||
|
this.muteVideo = this.muteVideo.bind(this);
|
||||||
|
this.unmuteVideo = this.unmuteVideo.bind(this);
|
||||||
|
this.emptyVideoTrack = this.emptyVideoTrack.bind(this);
|
||||||
|
this.handleDataChannelEvent = this.handleDataChannelEvent.bind(this);
|
||||||
|
this.handleDataChannelError = this.handleDataChannelError.bind(this);
|
||||||
|
this.handleDataChannelMessage = this.handleDataChannelMessage.bind(this);
|
||||||
|
this.handleDataChannelOpen = this.handleDataChannelOpen.bind(this);
|
||||||
|
this.handleDataChannelClose = this.handleDataChannelClose.bind(this);
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const topic = this.props.tinode.getTopic(this.props.topic);
|
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||||
@@ -10735,6 +10778,10 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
const stream = this.state.localStream;
|
const stream = this.state.localStream;
|
||||||
stream.getTracks().forEach(track => {
|
stream.getTracks().forEach(track => {
|
||||||
pc.addTrack(track, stream);
|
pc.addTrack(track, stream);
|
||||||
|
if (track.kind == 'video' && !this.localStreamConstraints.video) {
|
||||||
|
track.stop();
|
||||||
|
stream.removeTrack(track);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
onInfo(info) {
|
onInfo(info) {
|
||||||
@@ -10765,23 +10812,39 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
emptyVideoTrack() {
|
||||||
|
const width = 640;
|
||||||
|
const height = 480;
|
||||||
|
const canvas = Object.assign(document.createElement("canvas"), {
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
});
|
||||||
|
canvas.getContext('2d').fillRect(0, 0, width, height);
|
||||||
|
const stream = canvas.captureStream(0);
|
||||||
|
return Object.assign(stream.getVideoTracks()[0], {
|
||||||
|
enabled: false
|
||||||
|
});
|
||||||
|
}
|
||||||
start() {
|
start() {
|
||||||
if (this.state.localStream) {
|
if (this.state.localStream) {
|
||||||
this.props.onError(this.props.intl.formatMessage(messages.already_in_call), 'info');
|
this.props.onError(this.props.intl.formatMessage(messages.already_in_call), 'info');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.props.callState == _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_IN_PROGRESS) {
|
if (this.props.callState == _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_IN_PROGRESS) {
|
||||||
this.props.onInvite(this.props.topic, this.props.seq, this.props.callState);
|
this.props.onInvite(this.props.topic, this.props.seq, _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_IN_PROGRESS, this.props.callAudioOnly);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
navigator.mediaDevices.getUserMedia(this.localStreamConstraints).then(stream => {
|
navigator.mediaDevices.getUserMedia(this.localStreamConstraints).then(stream => {
|
||||||
|
if (!this.localStreamConstraints.video) {
|
||||||
|
stream.addTrack(this.emptyVideoTrack());
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
localStream: stream,
|
localStream: stream,
|
||||||
waitingForPeer: true
|
waitingForPeer: true
|
||||||
});
|
});
|
||||||
this.localRef.current.srcObject = stream;
|
this.localRef.current.srcObject = stream;
|
||||||
DIALING_SOUND.play();
|
DIALING_SOUND.play();
|
||||||
this.props.onInvite(this.props.topic, this.props.seq, this.props.callState);
|
this.props.onInvite(this.props.topic, this.props.seq, this.props.callState, this.props.callAudioOnly);
|
||||||
}).catch(this.handleGetUserMediaError);
|
}).catch(this.handleGetUserMediaError);
|
||||||
}
|
}
|
||||||
stop() {
|
stop() {
|
||||||
@@ -10801,6 +10864,10 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
this.state.pc.onicegatheringstatechange = null;
|
this.state.pc.onicegatheringstatechange = null;
|
||||||
this.state.pc.onnegotiationneeded = null;
|
this.state.pc.onnegotiationneeded = null;
|
||||||
this.state.pc.onicecandidateerror = null;
|
this.state.pc.onicecandidateerror = null;
|
||||||
|
this.state.pc.ondatachannel = null;
|
||||||
|
if (this.state.dataChannel) {
|
||||||
|
this.state.dataChannel.close();
|
||||||
|
}
|
||||||
this.state.pc.close();
|
this.state.pc.close();
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -10826,6 +10893,41 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
el.srcObject = null;
|
el.srcObject = null;
|
||||||
el.src = '';
|
el.src = '';
|
||||||
}
|
}
|
||||||
|
handleDataChannelError(error) {
|
||||||
|
console.error('data channel error', error);
|
||||||
|
}
|
||||||
|
handleDataChannelMessage(event) {
|
||||||
|
switch (event.data) {
|
||||||
|
case VIDEO_MUTED_EVENT:
|
||||||
|
this.setState({
|
||||||
|
remoteVideoLive: false
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case VIDEO_UNMUTED_EVENT:
|
||||||
|
this.setState({
|
||||||
|
remoteVideoLive: true
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleDataChannelOpen(event) {
|
||||||
|
if (!this.state.audioOnly) {
|
||||||
|
event.target.send(VIDEO_UNMUTED_EVENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleDataChannelClose() {}
|
||||||
|
handleDataChannelEvent(event) {
|
||||||
|
const channel = event.channel;
|
||||||
|
channel.onerror = this.handleDataChannelError;
|
||||||
|
channel.onmessage = this.handleDataChannelMessage;
|
||||||
|
channel.onopen = this.handleDataChannelOpen;
|
||||||
|
channel.onclose = this.handleDataChannelClose;
|
||||||
|
this.setState({
|
||||||
|
dataChannel: channel
|
||||||
|
});
|
||||||
|
}
|
||||||
createPeerConnection() {
|
createPeerConnection() {
|
||||||
const iceServers = this.props.tinode.getServerParam('iceServers', null);
|
const iceServers = this.props.tinode.getServerParam('iceServers', null);
|
||||||
const pc = iceServers ? new RTCPeerConnection({
|
const pc = iceServers ? new RTCPeerConnection({
|
||||||
@@ -10838,6 +10940,14 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
pc.onnegotiationneeded = this.handleNegotiationNeededEvent;
|
pc.onnegotiationneeded = this.handleNegotiationNeededEvent;
|
||||||
pc.onicecandidateerror = this.handleIceCandidateErrorEvent;
|
pc.onicecandidateerror = this.handleIceCandidateErrorEvent;
|
||||||
pc.ontrack = this.handleTrackEvent;
|
pc.ontrack = this.handleTrackEvent;
|
||||||
|
pc.ondatachannel = this.handleDataChannelEvent;
|
||||||
|
const channel = pc.createDataChannel("events", {
|
||||||
|
ordered: true
|
||||||
|
});
|
||||||
|
channel.onerror = this.handleDataChannelError;
|
||||||
|
channel.onmessage = this.handleDataChannelMessage;
|
||||||
|
channel.onopen = this.handleDataChannelOpen;
|
||||||
|
channel.onclose = this.handleDataChannelClose;
|
||||||
this.setState({
|
this.setState({
|
||||||
pc: pc,
|
pc: pc,
|
||||||
waitingForPeer: false
|
waitingForPeer: false
|
||||||
@@ -10850,7 +10960,7 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
this.setState({
|
this.setState({
|
||||||
callInitialSetupComplete: true
|
callInitialSetupComplete: true
|
||||||
}, _ => this.drainRemoteIceCandidatesCache());
|
}, _ => this.drainRemoteIceCandidatesCache());
|
||||||
}).catch(this.reportError);
|
}).catch(err => this.reportError(err));
|
||||||
}
|
}
|
||||||
reportError(err) {
|
reportError(err) {
|
||||||
this.props.onError(err.message, 'err');
|
this.props.onError(err.message, 'err');
|
||||||
@@ -10858,15 +10968,16 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
canSendOffer() {
|
canSendOffer() {
|
||||||
return this.isOutgoingCall || this.state.callInitialSetupComplete;
|
return this.isOutgoingCall || this.state.callInitialSetupComplete;
|
||||||
}
|
}
|
||||||
handleNegotiationNeededEvent() {
|
handleNegotiationNeededEvent(event) {
|
||||||
|
const pc = event.target;
|
||||||
if (!this.canSendOffer()) {
|
if (!this.canSendOffer()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.state.pc.createOffer().then(offer => {
|
pc.createOffer().then(offer => {
|
||||||
return this.state.pc.setLocalDescription(offer);
|
return pc.setLocalDescription(offer);
|
||||||
}).then(_ => {
|
}).then(_ => {
|
||||||
this.props.onSendOffer(this.props.topic, this.props.seq, this.state.pc.localDescription.toJSON());
|
this.props.onSendOffer(this.props.topic, this.props.seq, pc.localDescription.toJSON());
|
||||||
}).catch(this.reportError);
|
}).catch(err => this.reportError(err));
|
||||||
}
|
}
|
||||||
handleIceCandidateErrorEvent(event) {
|
handleIceCandidateErrorEvent(event) {
|
||||||
console.warn("ICE candidate error:", event);
|
console.warn("ICE candidate error:", event);
|
||||||
@@ -10879,14 +10990,24 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
handleNewICECandidateMsg(info) {
|
handleNewICECandidateMsg(info) {
|
||||||
const candidate = new RTCIceCandidate(info.payload);
|
const candidate = new RTCIceCandidate(info.payload);
|
||||||
if (this.state.callInitialSetupComplete) {
|
if (this.state.callInitialSetupComplete) {
|
||||||
this.state.pc.addIceCandidate(candidate).catch(this.reportError);
|
this.state.pc.addIceCandidate(candidate).catch(err => {
|
||||||
|
if (candidate.candidate) {
|
||||||
|
this.reportError(err);
|
||||||
|
}
|
||||||
|
console.warn("Error adding new ice candidate", candidate, err);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.remoteIceCandidatesCache.push(candidate);
|
this.remoteIceCandidatesCache.push(candidate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drainRemoteIceCandidatesCache() {
|
drainRemoteIceCandidatesCache() {
|
||||||
this.remoteIceCandidatesCache.forEach(candidate => {
|
this.remoteIceCandidatesCache.forEach(candidate => {
|
||||||
this.state.pc.addIceCandidate(candidate).catch(this.reportError);
|
this.state.pc.addIceCandidate(candidate).catch(err => {
|
||||||
|
if (candidate.candidate) {
|
||||||
|
this.reportError(err);
|
||||||
|
}
|
||||||
|
console.warn("Error adding cached ice candidate", candidate, err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
this.remoteIceCandidatesCache = [];
|
this.remoteIceCandidatesCache = [];
|
||||||
}
|
}
|
||||||
@@ -10899,7 +11020,7 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleSignalingStateChangeEvent(event) {
|
handleSignalingStateChangeEvent(event) {
|
||||||
if (this.state.pc.signalingState == 'closed') {
|
if (event.target.signalingState == 'closed') {
|
||||||
this.handleCloseClick();
|
this.handleCloseClick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10909,27 +11030,32 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
handleGetUserMediaError(e) {
|
handleGetUserMediaError(e) {
|
||||||
|
console.error("Error opening camera and/or microphone", e);
|
||||||
switch (e.name) {
|
switch (e.name) {
|
||||||
case 'NotFoundError':
|
case 'NotFoundError':
|
||||||
this.reportError(e.message);
|
this.reportError(e);
|
||||||
break;
|
break;
|
||||||
case 'SecurityError':
|
case 'SecurityError':
|
||||||
case 'PermissionDeniedError':
|
case 'PermissionDeniedError':
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.reportError(e.message);
|
this.reportError(e);
|
||||||
console.error("Error opening your camera and/or microphone:", e.message);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.handleCloseClick();
|
this.handleCloseClick();
|
||||||
}
|
}
|
||||||
handleVideoOfferMsg(info) {
|
handleVideoOfferMsg(info) {
|
||||||
let localStream = null;
|
let localStream = null;
|
||||||
const pc = this.createPeerConnection();
|
const pc = this.state.pc ? this.state.pc : this.createPeerConnection();
|
||||||
const desc = new RTCSessionDescription(info.payload);
|
const desc = new RTCSessionDescription(info.payload);
|
||||||
pc.setRemoteDescription(desc).then(_ => {
|
pc.setRemoteDescription(desc).then(_ => {
|
||||||
return navigator.mediaDevices.getUserMedia(this.localStreamConstraints);
|
return navigator.mediaDevices.getUserMedia(this.localStreamConstraints);
|
||||||
}).then(stream => {
|
}).then(stream => {
|
||||||
|
let dummyVideo;
|
||||||
|
if (!this.localStreamConstraints.video) {
|
||||||
|
dummyVideo = this.emptyVideoTrack();
|
||||||
|
stream.addTrack(dummyVideo);
|
||||||
|
}
|
||||||
localStream = stream;
|
localStream = stream;
|
||||||
this.localRef.current.srcObject = stream;
|
this.localRef.current.srcObject = stream;
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -10938,6 +11064,10 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
localStream.getTracks().forEach(track => {
|
localStream.getTracks().forEach(track => {
|
||||||
pc.addTrack(track, localStream);
|
pc.addTrack(track, localStream);
|
||||||
});
|
});
|
||||||
|
if (dummyVideo) {
|
||||||
|
dummyVideo.stop();
|
||||||
|
stream.removeTrack(dummyVideo);
|
||||||
|
}
|
||||||
}).then(_ => {
|
}).then(_ => {
|
||||||
return pc.createAnswer();
|
return pc.createAnswer();
|
||||||
}).then(answer => {
|
}).then(answer => {
|
||||||
@@ -10969,35 +11099,89 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
this.stop();
|
this.stop();
|
||||||
this.props.onHangup(this.props.topic, this.props.seq);
|
this.props.onHangup(this.props.topic, this.props.seq);
|
||||||
}
|
}
|
||||||
toggleMedia(kind) {
|
muteVideo() {
|
||||||
|
if (!this.state.pc || !this.state.dataChannel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const stream = this.state.localStream;
|
const stream = this.state.localStream;
|
||||||
stream.getTracks().forEach(track => {
|
const t = stream.getVideoTracks()[0];
|
||||||
if (track.kind != kind) {
|
t.enabled = false;
|
||||||
return;
|
t.stop();
|
||||||
}
|
stream.removeTrack(t);
|
||||||
track.enabled = !track.enabled;
|
this.state.dataChannel.send(VIDEO_MUTED_EVENT);
|
||||||
|
this.setState({
|
||||||
|
videoToggleInProgress: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
unmuteVideo() {
|
||||||
|
if (!this.state.pc || !this.state.dataChannel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.mediaDevices.getUserMedia({
|
||||||
|
video: true
|
||||||
|
}).then(stream => {
|
||||||
|
this.localRef.current.srcObject = null;
|
||||||
|
const sender = this.state.pc.getSenders().find(s => s.track.kind == 'video');
|
||||||
|
const track = stream.getVideoTracks()[0];
|
||||||
|
stream.removeTrack(track);
|
||||||
|
this.state.localStream.addTrack(track);
|
||||||
|
return sender.replaceTrack(track);
|
||||||
|
}).then(_ => {
|
||||||
|
this.localRef.current.srcObject = this.state.localStream;
|
||||||
|
this.state.dataChannel.send(VIDEO_UNMUTED_EVENT);
|
||||||
|
}).catch(err => this.handleGetUserMediaError(err)).finally(_ => {
|
||||||
|
this.setState({
|
||||||
|
videoToggleInProgress: false
|
||||||
|
});
|
||||||
});
|
});
|
||||||
this.forceUpdate();
|
|
||||||
}
|
}
|
||||||
handleToggleCameraClick() {
|
handleToggleCameraClick() {
|
||||||
this.toggleMedia('video');
|
if (this.state.videoToggleInProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tracks = this.state.localStream.getVideoTracks();
|
||||||
|
this.setState({
|
||||||
|
videoToggleInProgress: true
|
||||||
|
}, _ => {
|
||||||
|
if (tracks && tracks.length > 0 && tracks[0].enabled && tracks[0].readyState == 'live') {
|
||||||
|
this.muteVideo();
|
||||||
|
} else {
|
||||||
|
this.unmuteVideo();
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
audioOnly: !this.state.audioOnly
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
handleToggleMicClick() {
|
handleToggleMicClick() {
|
||||||
this.toggleMedia('audio');
|
const stream = this.state.localStream;
|
||||||
|
const t = stream.getAudioTracks()[0];
|
||||||
|
t.enabled = !t.enabled;
|
||||||
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const remoteActive = this.remoteRef.current && this.remoteRef.current.srcObject;
|
const audioTracks = this.state.localStream && this.state.localStream.getAudioTracks();
|
||||||
const disabled = !(this.state.localStream && this.state.localStream.getTracks());
|
const videoTracks = !this.state.audioOnly && this.state.localStream && this.state.localStream.getVideoTracks();
|
||||||
const audioIcon = this.state.localStream && this.state.localStream.getAudioTracks()[0].enabled ? 'mic' : 'mic_off';
|
const disabled = !this.state.pc || !this.state.dataChannel || !(audioTracks && audioTracks[0]);
|
||||||
const videoIcon = this.state.localStream && this.state.localStream.getVideoTracks()[0].enabled ? 'videocam' : 'videocam_off';
|
const audioIcon = audioTracks && audioTracks[0] && audioTracks[0].enabled ? 'mic' : 'mic_off';
|
||||||
|
const videoIcon = videoTracks && videoTracks[0] && videoTracks[0].enabled && videoTracks[0].readyState == 'live' ? 'videocam' : 'videocam_off';
|
||||||
const peerTitle = (0,_lib_utils_js__WEBPACK_IMPORTED_MODULE_5__.clipStr)(this.props.title, _config_js__WEBPACK_IMPORTED_MODULE_3__.MAX_PEER_TITLE_LENGTH);
|
const peerTitle = (0,_lib_utils_js__WEBPACK_IMPORTED_MODULE_5__.clipStr)(this.props.title, _config_js__WEBPACK_IMPORTED_MODULE_3__.MAX_PEER_TITLE_LENGTH);
|
||||||
const pulseAnimation = this.state.waitingForPeer ? ' pulse' : '';
|
const pulseAnimation = this.state.waitingForPeer ? ' pulse' : '';
|
||||||
|
let removeActive = false;
|
||||||
|
if (this.remoteRef.current && this.remoteRef.current.srcObject && this.state.remoteVideoLive) {
|
||||||
|
const rstream = this.remoteRef.current.srcObject;
|
||||||
|
if (rstream.getVideoTracks().length > 0) {
|
||||||
|
const t = rstream.getVideoTracks()[0];
|
||||||
|
removeActive = t.enabled && t.readyState == 'live';
|
||||||
|
}
|
||||||
|
}
|
||||||
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
id: "video-container"
|
id: "video-container"
|
||||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
id: "video-container-panel"
|
id: "video-container-panel"
|
||||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
className: "call-party self"
|
className: "call-party self",
|
||||||
|
disabled: this.state.audioOnly
|
||||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("video", {
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("video", {
|
||||||
ref: this.localRef,
|
ref: this.localRef,
|
||||||
autoPlay: true,
|
autoPlay: true,
|
||||||
@@ -11012,12 +11196,13 @@ class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompon
|
|||||||
"value": "You"
|
"value": "You"
|
||||||
}]
|
}]
|
||||||
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
className: "call-party peer"
|
className: "call-party peer",
|
||||||
|
disabled: !removeActive
|
||||||
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("video", {
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("video", {
|
||||||
ref: this.remoteRef,
|
ref: this.remoteRef,
|
||||||
autoPlay: true,
|
autoPlay: true,
|
||||||
playsInline: true
|
playsInline: true
|
||||||
}), remoteActive ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
}), removeActive ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
className: "caller-name inactive"
|
className: "caller-name inactive"
|
||||||
}, peerTitle) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
}, peerTitle) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
className: `caller-card${pulseAnimation}`
|
className: `caller-card${pulseAnimation}`
|
||||||
@@ -11116,7 +11301,7 @@ class CallStatus extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCompo
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (this.props.callState == 'started' && !this.props.duration) {
|
} else if (['accepted', 'started'].includes(this.props.callState) && !this.props.duration) {
|
||||||
duration = react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
duration = react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
id: "call_in_progress",
|
id: "call_in_progress",
|
||||||
defaultMessage: [{
|
defaultMessage: [{
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+45
-24
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
// This is a generated file. Don't edit.
|
// This is a generated file. Don't edit.
|
||||||
|
|
||||||
const PACKAGE_VERSION = "0.21.0-beta1";
|
const PACKAGE_VERSION = "0.21.0-beta2";
|
||||||
|
|||||||
Reference in New Issue
Block a user