diff --git a/src/app/Http/Controllers/API/V4/OpenViduController.php b/src/app/Http/Controllers/API/V4/OpenViduController.php
index 12cbbd85..065556bf 100644
--- a/src/app/Http/Controllers/API/V4/OpenViduController.php
+++ b/src/app/Http/Controllers/API/V4/OpenViduController.php
@@ -1,49 +1,55 @@
user();
$room = \App\OpenVidu\Room::where('name', $id)->first();
// this isn't a room, bye bye
if (!$room) {
return response()->json(['status' => 'error'], 404);
}
// there's no existing session
if (!$room->hasSession()) {
// TODO: only the room owner should be able to create the session
$room->createSession();
}
- $json = $room->getSessionToken('PUBLISHER');
+ $response = $room->getSessionToken('PUBLISHER');
- return response()->json($json, 200);
+ if (!empty(request()->input('screenShare'))) {
+ $add_token = $room->getSessionToken('PUBLISHER');
+
+ $response['shareToken'] = $add_token['token'];
+ }
+
+ return response()->json($response, 200);
}
/**
* Webhook as triggered from OpenVidu server
*
* @param \Illuminate\Http\Request $request The API request.
*
* @return \Illuminate\Http\Response The response
*/
public function webhook(Request $request)
{
return response('Success', 200);
}
}
diff --git a/src/resources/js/meet.js b/src/resources/js/meet.js
index 108a2ebe..20d5928c 100644
--- a/src/resources/js/meet.js
+++ b/src/resources/js/meet.js
@@ -1,89 +1,91 @@
/**
* Application code for the Meet UI
*/
import routes from './routes-meet.js'
window.routes = routes
require('./bootstrap')
import AppComponent from '../vue/Meet/App'
import MenuComponent from '../vue/Widgets/Menu'
import store from './store'
const app = new Vue({
el: '#app',
components: {
AppComponent,
MenuComponent,
},
store,
router: window.router,
data() {
return {
isLoading: true,
}
},
methods: {
errorPage(code, msg) {
// Until https://github.com/vuejs/vue-router/issues/977 is implemented
// we can't really use router to display error page as it has two side
// effects: it changes the URL and adds the error page to browser history.
// For now we'll be replacing current view with error page "manually".
const map = {
400: "Bad request",
401: "Unauthorized",
403: "Access denied",
404: "Not found",
405: "Method not allowed",
500: "Internal server error"
}
if (!msg) msg = map[code] || "Unknown Error"
const error_page = `
`
$('#app').children(':not(nav)').remove()
$('#app').append(error_page)
},
errorHandler(error) {
if (!error.response) {
// TODO: probably network connection error
} else {
this.errorPage(error.response.status, error.response.statusText)
}
}
}
})
// Add a axios request interceptor
window.axios.interceptors.request.use(
config => {
// We're connecting to the API on the main domain
config.url = window.config['app.url'] + config.url
return config
},
error => {
// Do something with request error
return Promise.reject(error)
}
)
// Register additional icons
import { library } from '@fortawesome/fontawesome-svg-core'
import {
+ faDesktop,
faExpand,
faMicrophone,
faPowerOff,
faVideo
} from '@fortawesome/free-solid-svg-icons'
// Register only these icons we need
library.add(
+ faDesktop,
faExpand,
faMicrophone,
faPowerOff,
faVideo
)
diff --git a/src/resources/js/meet/app.js b/src/resources/js/meet/app.js
index c397277d..49e6f4aa 100644
--- a/src/resources/js/meet/app.js
+++ b/src/resources/js/meet/app.js
@@ -1,229 +1,336 @@
import { OpenVidu } from 'openvidu-browser'
function Meet(container)
{
- let OV // OpenVidu object to initialize a session
- let session // Session object where the user will connect
- let publisher // Publisher object which the user will publish
- let sessionId // Unique identifier of the session
- let audioEnabled = true // True if the audio track of publisher is active
- let videoEnabled = true // True if the video track of publisher is active
- let numOfVideos = 0 // Keeps track of the number of videos that are being shown
- let audioSource = '' // Currently selected microphone
- let videoSource = '' // Currently selected camera
+ let OV // OpenVidu object to initialize a session
+ let session // Session object where the user will connect
+ let publisher // Publisher object which the user will publish
+ let sessionId // Unique identifier of the session
+ let audioEnabled = true // True if the audio track of publisher is active
+ let videoEnabled = true // True if the video track of publisher is active
+ let numOfVideos = 0 // Keeps track of the number of videos that are being shown
+ let audioSource = '' // Currently selected microphone
+ let videoSource = '' // Currently selected camera
+ let sessionData
+
+ let screenOV // OpenVidu object to initialize a screen sharing session
+ let screenSession // Session object where the user will connect for screen sharing
+ let screenPublisher // Publisher object which the user will publish the screen sharing
let publisherDefaults = {
publishAudio: true, // Whether to start publishing with your audio unmuted or not
publishVideo: true, // Whether to start publishing with your video enabled or not
resolution: '640x480', // The resolution of your video
frameRate: 30, // The frame rate of your video
mirror: true // Whether to mirror your local video or not
}
- let cameras = [] // List of user video devices
- let microphones = [] // List of user audio devices
+ let cameras = [] // List of user video devices
+ let microphones = [] // List of user audio devices
OV = new OpenVidu()
+ screenOV = new OpenVidu()
// if there's anything to do, do it here.
//OV.setAdvancedConfiguration(config)
// Disconnect participant on browser's window closed
/*
window.addEventListener('beforeunload', () => {
if (session) session.disconnect();
})
*/
// Public methods
+ this.isScreenSharingSupported = isScreenSharingSupported
this.joinRoom = joinRoom
this.leaveRoom = leaveRoom
- this.muteAudio = muteAudio
- this.muteVideo = muteVideo
this.setup = setup
this.setupSetAudioDevice = setupSetAudioDevice
this.setupSetVideoDevice = setupSetVideoDevice
+ this.switchAudio = switchAudio
+ this.switchScreen = switchScreen
+ this.switchVideo = switchVideo
function setup(videoElement, success_callback, error_callback) {
publisher = OV.initPublisher(null, publisherDefaults)
publisher.once('accessDenied', error => {
error_callback(error)
})
publisher.once('accessAllowed', async () => {
let mediaStream = publisher.stream.getMediaStream()
let videoStream = mediaStream.getVideoTracks()[0]
let audioStream = mediaStream.getAudioTracks()[0]
audioEnabled = !!audioStream
videoEnabled = !!videoStream
publisher.addVideoElement(videoElement)
const devices = await OV.getDevices()
devices.forEach(device => {
// device's props: deviceId, kind, label
if (device.kind == 'videoinput') {
cameras.push(device)
if (videoStream && videoStream.label == device.label) {
videoSource = device.deviceId
}
} else if (device.kind == 'audioinput') {
microphones.push(device)
if (audioStream && audioStream.label == device.label) {
audioSource = device.deviceId
}
}
})
success_callback({
microphones,
cameras,
audioSource,
videoSource,
audioEnabled,
videoEnabled
})
})
}
async function setupSetAudioDevice(deviceId) {
if (!deviceId) {
publisher.publishAudio(false)
audioEnabled = false
} else if (deviceId == audioSource) {
publisher.publishAudio(true)
audioEnabled = true
} else {
/*
let mediaStream = publisher.stream.getMediaStream()
let audioStream = mediaStream.getAudioTracks()[0]
audioStream.stop()
publisher = OV.initPublisher(null, properties);
publisher.addVideoElement(videoElement)
*/
// FIXME: None of this is working
let properties = Object.assign({}, publisherDefaults, {
publishAudio: true,
publishVideo: videoEnabled,
audioSource: deviceId,
videoSource: videoSource
})
await OV.getUserMedia(properties)
.then(async (mediaStream) => {
const track = mediaStream.getAudioTracks()[0]
await publisher.replaceTrack(track)
audioEnabled = true
})
}
return audioEnabled
}
function setupSetVideoDevice(deviceId) {
if (!deviceId) {
publisher.publishVideo(false)
videoEnabled = false
} else if (deviceId == videoSource) {
publisher.publishVideo(true)
videoEnabled = true
} else {
// TODO
}
return videoEnabled
}
+ /**
+ * Join the room session
+ *
+ * @param data Session metadata (session, token, shareToken)
+ */
function joinRoom(data) {
+ // TODO
+ data.params = {
+ clientData: 'Test', // user nickname
+ avatar: undefined // avatar image
+ }
+
+ sessionData = data
sessionId = data.session
// Init a session
session = OV.initSession()
// On every new Stream received...
session.on('streamCreated', function (event) {
// Subscribe to the Stream to receive it
let subscriber = session.subscribe(event.stream, addVideoWrapper(container));
// When the new video is added to DOM, update the page layout to fit one more participant
subscriber.on('videoElementCreated', (event) => {
numOfVideos++
updateLayout()
})
})
// On every new Stream destroyed...
session.on('streamDestroyed', (event) => {
// Update the page layout
numOfVideos--
updateLayout()
})
- // TODO
- let params = {
- clientData: 'Test', // user nickname
- avatar: undefined // avatar image
- }
-
// Connect with the token
- session.connect(data.token, params)
+ session.connect(data.token, data.params)
.then(() => {
publisher.createVideoElement(addVideoWrapper(container), 'PREPEND')
// When our HTML video has been added to DOM...
publisher.on('videoElementCreated', (event) => {
$(event.element).addClass('publisher')
.prop('muted', true) // Mute local video to avoid feedback
// When your own video is added to DOM, update the page layout to fit it
numOfVideos++
updateLayout()
})
// Publish the stream
session.publish(publisher)
})
.catch(error => {
- console.log('There was an error connecting to the session:', error.code, error.message);
+ console.error('There was an error connecting to the session:', error.code, error.message);
})
}
+
+ /**
+ * Leave the room (disconnect)
+ */
function leaveRoom() {
- // Leave the session by calling 'disconnect' method over the Session object
if (session) {
session.disconnect();
}
+
+ if (screenSession) {
+ screenSession.disconnect();
+ }
}
- function muteAudio() {
+ /**
+ * Mute/Unmute audio for current session publisher
+ */
+ function switchAudio() {
audioEnabled = !audioEnabled
publisher.publishAudio(audioEnabled)
return audioEnabled
}
- function muteVideo() {
+ /**
+ * Mute/Unmute video for current session publisher
+ */
+ function switchVideo() {
videoEnabled = !videoEnabled
publisher.publishVideo(videoEnabled)
return videoEnabled
}
+ /**
+ * Switch on/off screen sharing
+ */
+ function switchScreen(callback) {
+ if (screenPublisher) {
+ screenSession.unpublish(screenPublisher)
+ screenPublisher = null
+
+ if (callback) {
+ callback(false)
+ }
+
+ return
+ }
+
+ screenConnect(callback)
+ }
+
+ function isScreenSharingSupported() {
+ return !!OV.checkScreenSharingCapabilities();
+ }
+
function updateLayout() {
// update the "matrix" layout
}
function addVideoWrapper(container) {
return $('').appendTo(container).get(0)
}
+
+ /**
+ * Initialize screen sharing session/publisher
+ */
+ function screenConnect(callback) {
+ if (!sessionData.shareToken) {
+ return false
+ }
+
+ let gotSession = !!screenSession
+
+ // Init screen sharing session
+ if (!gotSession) {
+ screenSession = screenOV.initSession();
+ }
+
+ let successFunc = function() {
+ screenSession.publish(screenPublisher)
+ if (callback) {
+ callback(true)
+ }
+ }
+
+ let errorFunc = function() {
+ screenPublisher = null
+ if (callback) {
+ callback(false)
+ }
+ }
+
+ // Init the publisher
+ let params = {
+ videoSource: 'screen',
+ publishAudio: false
+ }
+
+ screenPublisher = screenOV.initPublisher(null, params)
+
+ screenPublisher.once('accessAllowed', (event) => {
+ if (gotSession) {
+ successFunc()
+ } else {
+ screenSession.connect(sessionData.shareToken, sessionData.params)
+ .then(() => {
+ successFunc()
+ })
+ .catch(error => {
+ console.error('There was an error connecting to the session:', error.code, error.message);
+ errorFunc()
+ })
+ }
+ })
+
+ screenPublisher.once('accessDenied', () => {
+ console.info('ScreenShare: Access Denied')
+ errorFunc()
+ })
+ }
}
export default Meet
diff --git a/src/resources/sass/meet.scss b/src/resources/sass/meet.scss
index 8464f659..9ae1c129 100644
--- a/src/resources/sass/meet.scss
+++ b/src/resources/sass/meet.scss
@@ -1,42 +1,52 @@
#meet-component {
+ flex-grow: 1;
+ & + .filler {
+ display: none;
+ }
}
#meet-session-toolbar {
display: flex;
justify-content: center;
}
#meet-session-menu {
button {
font-size: 1.3rem;
padding: 0.5rem 1rem;
}
}
#meet-session {
display: flex;
justify-content: center;
+ flex-wrap: wrap;
}
.meet-video {
position: relative;
+ max-hwight: 480px;
+ max-width: 640px;
+ // Note: To make object-fit:cover working we have to set
+ // height in px on the wrapper element
video {
- display: block;
+ object-fit: cover;
width: 100%;
+ height: 100%;
}
}
#meet-setup {
}
#setup-preview {
display: flex;
video {
width: 100%;
transform: rotateY(180deg);
background: black;
}
}
diff --git a/src/resources/vue/Meet/Room.vue b/src/resources/vue/Meet/Room.vue
index d46caf90..1d3618c0 100644
--- a/src/resources/vue/Meet/Room.vue
+++ b/src/resources/vue/Meet/Room.vue
@@ -1,141 +1,155 @@
-