diff --git a/src/resources/js/meet/app.js b/src/resources/js/meet/app.js
--- a/src/resources/js/meet/app.js
+++ b/src/resources/js/meet/app.js
@@ -1009,6 +1009,7 @@
+ svgIcon('user', 'fas', 'watermark')
+ '
'
+ '
'
+ + '
'
+ '
'
+ '
'
+ '
'
@@ -1034,12 +1035,52 @@
if (params.isSelf) {
wrapper.find('.link-setup').removeClass('hidden').click(() => sessionData.onMediaSetup())
} else {
- // Enable audio mute button
- wrapper.find('.link-audio').removeClass('hidden')
+ let volumeInput = wrapper.find('.volume input')
+ let audioButton = wrapper.find('.link-audio')
+ let inVolume = false
+ let hideVolumeTimeout
+ let hideVolume = () => {
+ if (inVolume) {
+ hideVolumeTimeout = setTimeout(hideVolume, 1000)
+ } else {
+ volumeInput.parent().addClass('hidden')
+ }
+ }
+
+ // Enable and set up the audio mute button
+ audioButton.removeClass('hidden')
.on('click', e => {
let video = wrapper.find('video')[0]
+
video.muted = !video.muted
- wrapper.find('.link-audio')[video.muted ? 'addClass' : 'removeClass']('text-danger')
+ video.volume = video.muted ? 0 : 1
+
+ audioButton[video.muted ? 'addClass' : 'removeClass']('text-danger')
+ volumeInput.val(video.volume)
+ })
+ // Show the volume slider when mouse is over the audio mute/unmute button
+ .on('mouseenter', () => {
+ let video = wrapper.find('video')[0]
+
+ clearTimeout(hideVolumeTimeout)
+ volumeInput.parent().removeClass('hidden')
+ volumeInput.val(video.volume)
+ })
+ .on('mouseleave', () => {
+ hideVolumeTimeout = setTimeout(hideVolume, 1000)
+ })
+
+ // Set up the audio volume control
+ volumeInput
+ .on('mouseenter', () => { inVolume = true })
+ .on('mouseleave', () => { inVolume = false })
+ .on('change input', () => {
+ let video = wrapper.find('video')[0]
+ let volume = volumeInput.val()
+
+ video.volume = volume
+ video.muted = volume == 0
+ audioButton[video.muted ? 'addClass' : 'removeClass']('text-danger')
})
}
diff --git a/src/resources/themes/meet.scss b/src/resources/themes/meet.scss
--- a/src/resources/themes/meet.scss
+++ b/src/resources/themes/meet.scss
@@ -1,3 +1,19 @@
+@mixin range-track() {
+ background-color: $link-color !important;
+ height: 0.2em;
+ border: 0;
+ border-radius: 0.1em;
+}
+
+@mixin range-thumb() {
+ appearance: none;
+ background-color: $link-color;
+ width: 0.75em;
+ height: 0.75em;
+ border: none;
+ border-radius: 0.5em;
+}
+
.meet-nickname {
padding: 0;
line-height: 2em;
@@ -142,6 +158,39 @@
display: none;
}
}
+
+ .volume {
+ position: absolute;
+ bottom: 2.15em;
+ right: 2em;
+ border-radius: 1em;
+ background: rgba(#000, 0.7);
+ width: 2em;
+ height: 4em;
+ overflow: hidden; // for Edge
+
+ input {
+ appearance: none;
+ cursor: pointer;
+ width: 3em;
+ height: 2em;
+ margin-top: 3.5em;
+ transform-origin: 0 0;
+ transform: rotate(-90deg);
+ background: transparent;
+ outline: 0;
+
+ &::-ms-track { @include range-track; }
+ &::-moz-range-track { @include range-track; }
+ &::-webkit-slider-runnable-track { @include range-track; }
+
+ &::-ms-thumb { @include range-thumb; }
+ &::-moz-range-thumb { @include range-thumb; }
+ &::-webkit-slider-thumb { @include range-thumb; margin-top: -0.25em; }
+
+ &::-ms-tooltip { display: none; }
+ }
+ }
}
#meet-component {
diff --git a/src/tests/Browser/Meet/RoomControlsTest.php b/src/tests/Browser/Meet/RoomControlsTest.php
--- a/src/tests/Browser/Meet/RoomControlsTest.php
+++ b/src/tests/Browser/Meet/RoomControlsTest.php
@@ -74,7 +74,7 @@
}
/**
- * Test nickname and muting audio/video
+ * Test nickname and audio/video muting/volume controls
*
* @group openvidu
*/
@@ -221,6 +221,40 @@
->assertAudioMuted('video', false)
->assertVisible('.controls button.link-audio:not(.text-danger)');
});
+
+ // Test volume control
+ $guest->mouseover('@menu')
+ ->with('div.meet-video:not(.self)', function (Browser $browser) {
+ $browser->waitUntilMissing('.volume')
+ ->mouseover('.controls button.link-audio')
+ ->waitFor('.volume')
+ ->assertValue('.volume input', '1')
+ ->keys('.volume input', ['{arrow_down}'])
+ ->keys('.volume input', ['{arrow_down}'])
+ ->keys('.volume input', ['{arrow_down}'])
+ ->keys('.volume input', ['{arrow_down}'])
+ ->keys('.volume input', ['{arrow_down}'])
+ ->keys('.volume input', ['{arrow_down}'])
+ ->keys('.volume input', ['{arrow_down}'])
+ ->keys('.volume input', ['{arrow_down}'])
+ ->keys('.volume input', ['{arrow_down}'])
+ ->keys('.volume input', ['{arrow_down}'])
+ ->assertValue('.volume input', '0')
+ ->assertAudioMuted('video', true)
+ ->assertVisible('.controls button.link-audio.text-danger')
+ ->click('.controls button.link-audio')
+ ->assertAudioMuted('video', false)
+ ->assertValue('.volume input', '1')
+ ->click('.controls button.link-audio')
+ ->assertAudioMuted('video', true)
+ ->assertValue('.volume input', '0')
+ ->keys('.volume input', ['{arrow_up}'])
+ ->assertValue('.volume input', '0.1')
+ ->assertAudioMuted('video', false)
+ ->assertVisible('.controls button.link-audio:not(.text-danger)')
+ ->mouseover('.meet-nickname')
+ ->waitUntilMissing('.volume');
+ });
});
}