Magnoliyan Video Chat PRO

HTML5 video chat system


Thank you for purchasing this app.

Magnoliyan Video Chat PRO (mgVideoChat) is full featured video communication system with integrated text chat, available directly within your web browser. You can consider it as online web based Skype, but without the requirement to install any additional software, plugin nor flash.

There is unlimited number of use cases: online live video chat support, private rooms, community chat rooms...

Compared to standard Video Chat PRO version features: group (conference) video chat, file transfer, chat roulette mode

Unlike some other systems, Magnoliyan Video Chat PRO includes full featured server side "signaling" engine: you can see who is online, click to call, click to end call, send text messages. You can even mark certain users as operators which are able to receive calls while all others can just make calls.

Unlike some other systems, Magnoliyan Video Chat PRO includes full featured server side "signaling" engine: you can see who is online, click to call, click to end call, send text messages. You can even mark certain users as operators who are able to receive calls, while all others can just make calls - perfect for tech support.

In order to disallow anonymous users, there is special plugin system for authorization of visitors. You can easily customize it with your own web app login mechanism. Still, Magnoliyan Video Chat PRO comes with built in support for facebook login and wordpress accounts. It's even possible to use different authorization system per chat room.

Architecture

Deployment

Magnoliyan PHP chat server is websocket signaling central components. Magnoliayan jquery client script is HTML5 WebRTC app. Media (video/audio) communication is done directly (if possible) by the means of STUN servers. If direct traffic is not possible TURN servers are involved.

Server side

Step 1: copy files

Copy source/server folder contents to your domain.

Step 2: install libraries

Login to your server using ssh (ssh tutorials), enter the copied folder and run

php ~/composer.phar install

If you don't have composer installed please read this tutorial for more information.

Installing over composer is preffered method. However, if you for any reason have problems with it you can try Alternative PHP Install

Step 3: run the server

php bin/chat-server.php

Client side

Step 1: copy files

Copy source/client folder contents to your domain's public folder.

Step 2: create html page

Create standard html which should contain at least a container element for the video chat, ex

<div id="mgVideoChat"></div>

First we need to include files mgVideoChat depends on, and then mgVideoChat itself:

<!-- CSS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css">
<link rel="stylesheet" href="mgVideoChat/mgVideoChat-1.11.0.css">

<!-- JS -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" src="//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"></script>
<script src="mgVideoChat/mgVideoChat-1.11.0-min.js"></script>

Step 4: start mgVideoChat

All that is left is to initiate mgVideoChat plugin on the proper html element, when the document is ready:

$(document).ready(function(){
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://www.myserverdomain.com:8080?room=1' //domain:port and room id info
    });
});

Please read more details about client configuration options.

Installing over composer is preferred method. However, if you for any reason have problems with it you can:

Step 1: copy files

Copy source/server folder contents to your domain.

Step 2: copy vendors content

Download zip file and unzip its content in source/server/vendor folder.

Now you can run your server as defined in the Quick Start Guide or you can try Web Commander

Step 3: start via Web Commander

Update file source/web-commander/config.php especially with custom username and password (default: admin/admin).

From your web browser open source/web-commander/index.php and click Start

Step 4: test websocket connection

After the server is started you will be able to test websocket connection from the panel on the right.

Update domain and/or port property according to your server's setup and click to test.

If connection is successful you can update examples' source code with the same websocket parameters and open them.

Step 5: check log file

By default, when started from webcommander, the server output will be logged in web-commander.log file in the web-commander's folder. In order for this to work, the log file has to be writable by webserver. If you prefer, you can define other file path for loggin in web-commander's config.php file in key log_file.

Please read more details about server configuration options in bin/config.php file

This app uses the following Javascript libraries:

This app uses the following PHP library:

In order to run Magnoliyan Video Chat PRO server side you need:

In order to run Magnoliyan Video Chat PRO client side you need to:

HTTPS, SSL, WSS

Since Chrome 47 a camera/mic access is allowed only over https. For that reason you will be forced to use HTTPS protocol only.
Further, if you use HTTPS for your website, browser will not allow any other non-secure communication. So websocket communication must be secure as well, it must use wss instead of ws protocol.

The server part of the system itself cannot run wss (ws over ssl) for websockets, though there are possible 3rd party work-arounds with proxies described here. You should already make sure you will be able to apply one of them.

If you are already using Apache, the easiest way might be to use it as a proxy. In order to achive this you can enable Apache's proxy_wstunnel (sudo a2enmod proxy_wstunnel). In your domain .conf file (the actual file might vary on different OS) you can add, usually next to where you ssl certificate is defined:

SSLProxyEngine On
ProxyPass /wss/ ws://www.your_domain.com:8080/

For nginx things are similar, in the domain config you should add:

location /wss/ {
    proxy_pass http://www.your_domain.com:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400;
}
            



It's important to include the same proxy path, e.g. /wss/ in wsURL parameter.
$(document).ready(function(){
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'wss://www.your_domain.com/wss/?room=1'
    });
});

Video chat PRO server part is not conventional php page, but service which should run all the time in the background. So it's not triggered by web request, executed, killed but instead runs in the background as a process (service, daemon). In order to run it you need to execute command from the linux shell. Not all web developers are comfortable working with shell. For those I suggest reading tutorials like http://www.siteground.com/tutorials/ssh/ or similar. Here are a few topics which can help managing Video Chat PRO server side process.

Basics

In order to run the process you have to enter folder source/server/bin. To start the process in there just type and hit enter:

php chat-server.php

Start/Stop script

In order to simplify starting and stopping of the process we provided run.sh shell script in the same folder. To start the process execute:

./run.sh start

to stop:

./run.sh stop

Run in the background

Usually you need to have the process running after you logout from the shell. Here's the command to help

nohup php chat-server.php &

Log the output

If you want to have log file here's the way

nohup php chat-server.php > app.log &

You might want to store more info with config option debug set as true

Get process ID

To check if th process is running and get its id (pid) execute:

ps aux | grep [c]hat-server

The number in the second column should be process id (pid).

Stop (kill) the process

After obtaining the process id you can execute

kill -9 pid

Where pid should be replaced with actual value.

Client

jQuery plugin options array.

option description default possible values
wsURL Websocket URL to your server in format ws://domain:port?room=roomNumber ws://localhost:8080?room=1 string
debug Log messages to JS console false boolean
login function called on Login button - ex. custom popup, redirect to login page, etc. null function
tplMain URL to the main template relative to the running page /tpls/main.html string
tplConnections URL to the connections template relative to the running page /tpls/connections.html string
tplConnection URL to the single connection template relative to the running page /tpls/connection.html string
tplChat URL to the chat template relative to the running page /tpls/chat.html string
tplChatInput URL to the chat input box template relative to the running page /tpls/chat_input.html string
tplFile URL to the file progress template relative to the running page /tpls/file.html string
fileMaxSize Maximum size in bytes for a file to be transfered 512000 integer
sound.mp3 URL to the mp3 ring sound file relative to the running page /sounds/ring.mp3 string
sound.ogg URL to the ogg ring sound file relative to the running page /sounds/ring.ogg string
enableNotifications Enable desktop notifications when page is hidden true boolean

Events

Attach custom event handler to an event:

$('#mgVideoChat').mgVideoChat('on','connections',function(connections){
console.log('[mgVideoChat.connections]fired',connections);
});
event name description parameters
connections fired each time new connection (peer) is added or removed connections: Object
object with all connections objects as sub-keys
call_status fired each time a call status with a peer changes connectionId: int, status: string
statuses: idle, call_inviting, call_invited, call_accepting, call_accepted, sdp_offering, sdp_offered, sdp_answering, sdp_answered, call
chat_message fired each time a new text message received data: object
Object containing {connectionId: int, message: string}
logged fired when current user is logged in

Scripting

If you want to customize client side while reusing built-in functionality of jquery plugin you can leverage advanced javascript scripting api. Here are a few basic initial steps. For more details please inspect source code of mgVideoChat-1.11.0-min.js and its classes.

//global instaces of chat objects
var rtc, mgChat;

$(document).ready(function(){
    //init plugin
    mgChat = $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://' + wsDomain + ':' + wsPort + '?room=1'
    });
    //attach to plugin events
    //on call status change
    $('#mgVideoChat').mgVideoChat('on','call_status',function(connectionId, status){
        console.log('[mgVideoChat.call_status] fired',connectionId, status);
    });
    //on chat message
    $('#mgVideoChat').mgVideoChat('on','chat_message',function(data){
        console.log('[mgVideoChat.chat_message] fired', data);
    });
    //on logged in
    $('#mgVideoChat').mgVideoChat('on','logged',function(){
        console.log('[mgVideoChat.logged] fired');
    });                
    //get low level rtc object
    rtc = mgChat.getRtc();
    //attach to rtc object low level events
    rtc.on('chat_message', function(data) {
        console.log('[mgRtc.chat_message] fired', data);
    });

    $("#customSendMessage").click(function(){
        //log peer connections
        console.log(rtc.connections);
        var message = prompt("Enter message text", "Message");
        var connectionId = prompt("Enter destination connection id");
        if(message && connectionId){
            //use rtc object to send a message to a connection
            rtc.chatMessage(connectionId, message);
        }
    });

});

RTC object methods

method name description parameters
on attach event listener eventName: string, callback: function
accept accept call connectionId: int, opt: Object
invite initiate call connectionId: int, opt: Object
busy send busy signal connectionId: int
drop drop call connectionId: int, leaveConnection: bool, leaveStream: bool
chatMessage send text message connectionId: int, messageText: string

Server

PHP config.php options array.

option description default possible values
port Websocket server port 8080 integer
debug Echo debug messages to the output false boolean
allowedOrigins Array of domain names allowed to access your server. null allows all, but for production only your own domain should be allowed. null array
IpBlackList Array of black-listed IP addresses not allowed to connect to the server. null array
authAdapter Authorization adapter class name. Built-in classes are: MgRTC\Session\AuthSimple, MgRTC\Session\AuthFacebook, MgRTC\Session\AuthWordpress
If custom authorization class is needed it can be easily developed. The class has to implement interface MgRTC\Session\AuthInterface and has to implement single function:

/**
 * @param ConnectionInterface $conn
 * @param array $cookies
 * @return array
 */
function authUser(ConnectionInterface $conn, array $cookies);
                            
Built-in classes can serve as examples. Basically you should read $cookies to look for authorization info (sesson cookies). Also $conn->Config can be used to access global configuration settings if needed.
If an user is notg authorized function should return null. Otherwise, return array with user info:

return array(
    'provider'      => 'wordpress',
    'id'            => $userInfo->ID,
    'email'         => $userInfo->user_email,
    'name'          => $userInfo->display_name
);
                            
It should contain at least: provider name, user id, email, display name.
MgRTC\
Session\
AuthSimple
string
friendlistAdapter Friend-list adapter class name. This class is in charge to answer if one user is able to call/see other.
For example, even when two users are connected in the same room your custom class could decide if they are able to see/chat with each other. The class has to implement interface MgRTC\Friendlist\CallableInterface and has to implement single function:
/**
* If caller sees and can call callee
* 
* @param array $caller
* @param array $callee
* @return boolean
*/
function canCall(array $caller, array $callee);
Built-in CallableOperator can serve as example. Basically, based on users data you should be able to return true or false if one sees the other.
MgRTC\
Friendlist\
CallableOperator
string
operators Optionally here you can define array of user ids which are allowed to receive call, ex. technical support operators. Note that you can also define operators on a room level. null array
allowDuplicates Allow users with the same id to login from different locations at the same time true boolean
disableVideo Disable video calls (allow just audio) false boolean
disableAudio Disable audio calls (allow just video) false boolean
limit Maximum number of logged in user per room. Leave empty for unlimited. This option can be specified for particular room in room's configuration options. null integer
file Allow file transfer between peers. This option can be specified for particular room in room's configuration options. false boolean
group Activate conference or group mode for the chat. This option can be specified for particular room in room's configuration options. false boolean
roulette Activate chat roulette mode. This option can be specified for particular room in room's configuration options. false boolean
desktopShare Activate desktop sharing option (available only for chrome using an extension over https in view-only mode). This option can be specified for particular room in room's configuration options. false boolean
rooms Array of chat room numbers with defined custom authorization adapters per room:
array(
    1   => array(
        'authAdapter'   => 'MgRTC\Session\AuthSimple'
    ),
    2   => array(
        'authAdapter'   => 'MgRTC\Session\AuthFacebook'
    ),
    3   => array(
        'authAdapter'   => 'MgRTC\Session\AuthWordpress'
    ),
    4   => array(
        'authAdapter'   => 'MgRTC\Session\AuthSimple',
        'operators'     => array(11)
    ),
    'test%'   => array(
        'authAdapter'   => 'MgRTC\Session\AuthSimple',
        //...
    )
)
Room ID keys can even take pattern form, ex. 'test%' which means that all rooms which ID begins with test will have defined options.
null array
wordpress If MgRTC\Session\AuthWordpress active then this is required. Defines directory path to the wordpress installation:
array(
    'dir'           => '/path-to-wordpress'
)
null array
facebook If MgRTC\Session\AuthFacebook active then this is required. Defines facebook app information of a custom app on Facebook platform. Please read this tutorial about how to create custom facebook login app.
array(
    'appId'         => 'your-app-id',
    'secret'        => 'your-app-secret'
)
null array
simple If MgRTC\Session\AuthSimple active you can define here list of authorized members
array(
    'allowAnonim'   => true,
    'members'       => array(
        array('id' => 11, 'username' => 'operator1', 'password' => 'operator1', 'name'   => 'Tech Support')
    )
)
When allowAnonim is true no username/password is required to login
null array

If you need to implement custom login (authorization) mechanism, ex. in order to connect Video Chat to your custom CMS, you will need to develop custom authorization plugin and activate it in config.php authAdapter. All your class has to do is implement interface AuthInterface which has single method:

function authUser(ConnectionInterface $conn, array $cookies);

The function should inspect $cookies array and conclude if an user is logged in and if yes return array with user info:

return array(
    'provider'      => 'your cmsname',
    'id'            => 'user unique id',
    'email'         => 'user email',
    'name'          => 'user name'
);

If an user is not logged in, the function should return null

If you need to access settings from config.php you can do that via:

$conn->Config

As examples or templates you can use classes: AuthSimple, AuthFacebook or AuthWordpress

On the client side you can also customize login functionality. Take a look at login parameter in the client options. For example, if you customize this function you can integrate your own login dialog. Inside examples section, check Tech. support operator code for a custom login dialog example.

If you are having problems with audio and/or video between some peers, the problem is probably related to NATs/firewalls in between. Webrtc leverages technology STUN/TURN to help peers find the shortest path for media channel. While STUN servers enable direct p2p communication, TURN is there to help even if that fails and it's included in media traffic. Longer initialization snippet of jquery plugin can solve the problem since it involves more STUN/TURN options:

$('#mgVideoChat').mgVideoChat({
    wsURL: 'ws://yourdomain:your_port?room=your_room',
    rtc: {
        // Holds the STUN server to use for PeerConnections.
        pcConfig: {
            iceServers: [
                {"url": "stun:stun.l.google.com:19302"},
                {"url": "stun:stun.services.mozilla.com"},
                {
                    "url": "turn:8.34.221.6:3478",
                    "credential": "rIBICQW97vDh44NZdx5R2eDI6Yw=",
                    "username": "29227155:1387612335"
                },
                {
                    "url": "turn:8.34.221.6:3479",
                    "credential": "rIBICQW97vDh44NZdx5R2eDI6Yw=",
                    "username": "29227155:1387612335"
                },
                {
                  "url": "turn:8.34.221.6:3478?transport=udp",
                  "credential": "GEJJpHaSTpA7dRNBD2igwjoAB7E=",
                  "username": "48759000:1387612369"
                },
                {
                  "url": "turn:8.34.221.6:3478?transport=tcp",
                  "credential": "GEJJpHaSTpA7dRNBD2igwjoAB7E=",
                  "username": "48759000:1387612369"
                },
                {
                  "url": "turn:8.34.221.6:3479?transport=udp",
                  "credential": "GEJJpHaSTpA7dRNBD2igwjoAB7E=",
                  "username": "48759000:1387612369"
                },
                {
                  "url": "turn:8.34.221.6:3479?transport=tcp",
                  "credential": "GEJJpHaSTpA7dRNBD2igwjoAB7E=",
                  "username": "48759000:1387612369"
                }
            ]
        },
        pcConstraints: {"optional": [{"DtlsSrtpKeyAgreement": true}]},
        offerConstraints: {"optional": [], "mandatory": {}},
        mediaConstraints: {"audio": true, "video": true},
        sdpConstraints: {
            'mandatory': {
                'OfferToReceiveAudio': true,
                'OfferToReceiveVideo': true
            }
        },
        audio_receive_codec: 'opus/48000'
    }
});

basically just add rtc options to your existing setup.

If needed you can run your own STUN/TURN servers, just go for this open-source project and check this description

Installing COTURN server

coturn is open-source TURN/STUN solution available at https://code.google.com/p/coturn/. We recommend installing and running this server as explained here.

For authorization we recommend using configuration options use-auth-secret, static-auth-secret, realm.
In our source code you can find implementation for Turn REST API in folder source/turn-rest-api which can be used as temporary credentials provider. Please update config.php with relevant settings.

$ip = 'your server ip';
return array(
    'allowNoOrigin'         => true, //allow access when client does not provide origin header
    'secret'                => 'your_secret', //the same as "static-auth-secret" from /etc/turnserver.conf
    'users'                 => array('your_username'), //usernames
    'origins'               => array('http://www.yourserver.com', 'https://www.yourserver.com'), //allowed origins
    'uris'                  => array("turn:$ip:3478?transport=tcp", "turn:$ip:3478?transport=udp"), //turn/stun uris
    'ttl'                   => 86400 //time to live in secs for provided temporary credentials
);

The client side should first make ajax call to obtain list of TURN uris and then initiate Magnoliyan Chat Client. Please take a look at provided example.

Simple Authorization

VIEW DEMO


This authorization is for demonstration purposes only. It should not be used in production. It features simple popup dialog which saves identity to a cookie which is read and validated by Simple authorization server side adapter.

<script src="mgVideoChat/mgVideoChat-1.11.0-min.js"></script>
<script>
$(document).ready(function(){
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://www.magnoliyan.com:8080?room=1'
    });
});
</script>

Facebook Authorization

VIEW DEMO


In order to provide facebook login you need to create facebook app related to your domain. Please read this tutorial about how to create custom facebook login app.
Obviously server side has to have MgRTC\Session\AuthFacebook enabled.

<script src="mgVideoChat/mgVideoChat-1.11.0-min.js"></script>
<script>
$(document).ready(function(){
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://www.magnoliyan.com:8080?room=2',
        facebook: {
            appId      : 'your_app_id', // App ID - update this!
            channelUrl : '//magnoliyan.com/demos/facebook/channel.html', // Channel File
            status     : true, // check login status
            cookie     : true, // enable cookies to allow the server to access the session
            xfbml      : true  // parse XFBML
        }
    });
});
</script>

Wordpress Authorization

VIEW DEMO


For this demo we have standard wordpress installation with additional plugins:

The following html/javascript code is activating the jquery plugin and connects video chat to a proper room defined with wordpress authorization class at the server side

<!--raw-->
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" rel="stylesheet" />
<link href="/rtc-video/source/client/mgVideoChat/mgVideoChat-1.11.0.css" rel="stylesheet" />

<div id="mgVideoChat"></div>

<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" src="//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"></script>
<!-- Video Chat -->
<script type="text/javascript" src="/rtc-video/source/client/mgVideoChat/mgVideoChat-1.11.0-min.js"></script>

<script type="text/javascript">
$(document).ready(function(){
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://www.magnoliyan.com:8080/rtc-video/source/client/demos/wordpress/?room=3'
    });
});
</script>
<!--/raw-->

Please check wsURL parameter. The part /video-chat-pro/source/client/demos/wordpress/ is path of our wp installation, so you will need to adopt it to your case. If your wp is installed directly in domain root, then just have /

Wordpress HTTPS

Unfortunatelly, only text chat will work over http, you will need to setup https installation. We still suggest making all work as http then upgrade to https.
Please read here general HTTPS instructions and then continue with the rest here specific for wordpress.

Unfortunatelly, things are even more complicated for WP. You will need to alter wsURL parameter to be something like:

wsURL: 'wss://www.magnoliyan.com/video-chat-pro/source/client/demos/wordpress/wss/?room=3'

Again, mind the path! It's your wp installation root url path with appended /wss/

Then in your apache's domain config file you need additional proxy rule (if you wp is installed in the domain directly then you don't need this additional rule):

ProxyPass /video-chat-pro/source/client/demos/wordpress/wss/ ws://www.magnoliyan.com:8080/

and reload or restart apache!

Private rooms

VIEW DEMO

This demo explains how you can create room numbers on the fly so nobody else but visitors could know the room number.

//get query string param
function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
    results = regex.exec(location.search);
    return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
//update query strung parameter
function updateQueryStringParameter(uri, key, value) {
    var re = new RegExp("([?|&])" + key + "=.*?(&|$)", "i");
    var separator = uri.indexOf('?') !== -1 ? "&" : "?";
    if (uri.match(re)) {
        return uri.replace(re, '$1' + key + "=" + value + '$2');
    }
    else {
        return uri + separator + key + "=" + value;
    }
}
//get random number
function getRandomInt (min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

$(document).ready(function(){
    //read chat room
    var room = getParameterByName('room');
    //no private chat room?
    if(!room){
        //get one
        room = getRandomInt(1000, 100000);
        //redirect to chat room url
        window.location.href = updateQueryStringParameter(window.location.href,'room',room);
    }
    $('#privateUrl').text(window.location.href);
    //init video chat
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://www.magnoliyan.com:8080?room=' + room
    });
});

Tech Support

This demo explains how you operators configuration option to make tech. support page where visitors do not have to login and operators have to provide user/pass.
Here operators should login from here
and using different browsers you can login other users from here

Here's operators page code:

<div class="container">
    <div class="page-header">
        <h1>Magnoliyan Video Chat <small>Tech Support</small></h1>
        <p>User operator1/operator1 for demo login</p>
    </div>
    <div id="mgVideoChat"></div>
</div>
<div id="operatorLoginDialog" class="modal fade" data-focus-on="input:first" style="display: none">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                <h4 class="modal-title">Login</h4>
            </div>
            <div class="modal-body">
                <label class="sr-only" for="userName">Username</label>
                <input id="userName" class="form-control" type="text" data-tabindex="1" placeholder="Username">
                <label class="sr-only" for="userName">Password</label>
                <input id="password" class="form-control" type="password" data-tabindex="2" placeholder="Password">
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-primary login">Login</button>
            </div>
        </div>
    </div>
</div>
//set cookie via javascript
function setCookie( cookieName, cookieValue, days, domain){
    var domainString = domain ? ("; domain=" + domain) : '';
    document.cookie = cookieName + "=" + encodeURIComponent(cookieValue) + "; max-age=" + (60 * 60 * 24 * days) + "; path=/" + domainString;
};

$(document).ready(function(){
    //focus username in dialog
    $('#operatorLoginDialog').on('shown.bs.modal',function(){
        $('#operatorLoginDialog').find('#userName').focus();
    });
    //execute on login
    var onLogin = function(){
        if($('#operatorLoginDialog').find('#userName').val()){
            //set cookies for the server
            setCookie('mgVideoChatSimpleUser', $('#operatorLoginDialog').find('#userName').val(), 30, window.location.host);
            setCookie('mgVideoChatSimplePass', $('#operatorLoginDialog').find('#password').val(), 30, window.location.host);
            $('#operatorLoginDialog').modal('hide');
            //reload to use new cookies
            window.location.reload();
        }
    };
    //submit on enter key
    $('#operatorLoginDialog').find('#userName,#password').keypress(function(e){
        if (e.keyCode === 13) {
            onLogin();
            return false;
        }
    })
    $('#operatorLoginDialog').find('button.login').click(onLogin);
});

//init video chat
$(document).ready(function(){
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://www.magnoliyan.com:8080?room=4',
        login: function(callback){
            $('#operatorLoginDialog').modal('show');
        }
    });
});

Visitor's page is identical simple demo, just enters room 4.

And here's the server side relevant config:

'simple'            => array(
    'allowAnonim'   => true,
    'members'       => array(
        array('id' => 11, 'username' => 'operator1', 'password' => 'operator1', 'name'   => 'Tech Support')
    )
),
'rooms'             => array(
    4   => array(
        'authAdapter'   => 'MgRTC\Session\AuthSimple',
        'operators'     => array(11)
    )
)

Group chat

VIEW DEMO

This demo explains how you can activate group or conference mode for a room.
You should probably limit the number of user per chat room in this case since the network bandwidth per peer is limited and will not be able to handle too many video/audio connections.
This server side configuration snippet activates group mode:

'rooms'             => array(
    //...
    5   => array(
        'authAdapter'   => 'MgRTC\Session\AuthSimple',
        'group'         => true,
        'limit'         => 3
    )

and client side:

//init video chat
$(document).ready(function(){
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://www.magnoliyan.com:8080?room=5'
    });
});

Group private chat

VIEW DEMO

This example is special "combination" of private and group features from previous examples.
Room IDs are created on the fly and server side configuration defines options for room IDs which match defined pattern. This means that all dynamically generated ids which match room ID pattern will have these options This server side configuration snippet:

'rooms'             => array(
    //...
    'group_%'   => array(
        'authAdapter'   => 'MgRTC\Session\AuthSimple',
        'group'         => true,
        'limit'         => 3
    )
Note that pattern group_% will match all group ids beginning with group_.

Client side:

$(document).ready(function(){
    //read chat room
    var room = getParameterByName('room');
    //no private chat room?
    if(!room){
        //get one
        room = 'group_' + getRandomInt(1000, 100000);
        //redirect to chat room url
        window.location.href = updateQueryStringParameter(window.location.href,'room',room);
    }
    $('#privateUrl').text(window.location.href);
    //init video chat
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://' + wsDomain + ':' + wsPort + '?room=' + room
    });
});

Invite group/private chat

VIEW DEMO

This demo demonstrates scripting capabilites of mg jquery plugin.
There's independent button which an user can use to invite some of other connected visitors to join his group/private chat.
Group/private rooms share the same configuration as previous example.

But the main part is done on the client side. If query parameter room is not provided, we use default value 1 and we show regular chat.
Invate group triggers dialog which let you choose friends to join your private group chat.
They will receive link to the same page, but now with dynamically generated room parameter.

var rtc, mgChat, allFriends = [];

//render friends select options
function renderFriends(){
    $('#friends').html('');
    for (var connectionId in allFriends) {   
         $('#friends')
             .append($("<option></option>")
             .attr("value",connectionId)
             .text(allFriends[connectionId]['data']['userData']['name'])); 
    };
}

//get query string param
function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
    results = regex.exec(location.search);
    return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

//update query strung parameter
function updateQueryStringParameter(uri, key, value) {
    var re = new RegExp("([?|&])" + key + "=.*?(&|$)", "i");
    var separator = uri.indexOf('?') !== -1 ? "&" : "?";
    if (uri.match(re)) {
        return uri.replace(re, '$1' + key + "=" + value + '$2');
    }
    else {
        return uri + separator + key + "=" + value;
    }
}

function getInviteUrl(){
    return updateQueryStringParameter(window.location.href, 'room', generateRoomId());
}

//get random number
function getRandomInt (min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}            

function generateRoomId(){
    return 'group_' + getRandomInt(1000, 100000);
}

$(document).ready(function(){

    //read chat room
    var room = getParameterByName('room');
    if(!room){
        room = '1';
    } else {
        $('#inviteDialogBtn').remove();
    }                
    //chat object itself
    mgChat = $('#mgVideoChat').mgVideoChat({
        wsURL: wsUrlDefault + '?room=' + room
    });

    //store connections in global variable
    $('#mgVideoChat').mgVideoChat('on','connections',function(connections){
        allFriends = connections;
        //console.log('connections event', allFriends);
    });

    //invite dialog
    $('#inviteDialogBtn').click(function(){
        renderFriends();
        $('#inviteDialog').modal('show');
    });

    //chosen plugin for multiselect
    $('#inviteDialog').on('shown.bs.modal', function () {
        //rebuild it on dialog popup
        $('#friends').chosen('destroy').chosen();
    });                               

    $('#inviteBtn').click(function(){
        var url = getInviteUrl();
        var message = $("#invitationText").val() + ' ' + url;
        if($('#friends').find(":selected").length == 0){
            swal({title:"Error", text: "You have to select a friend!", type: "error" });
            return;
        }
        $('#friends').find(":selected").each(function(){
            mgChat.getRtc().chatMessage($(this).attr('value'), message);
        });
        swal({
            title:"Invited",
            text: 'Your friends are invited to join you at ' + url + '',
            type: "success",
            html: true,
            showCancelButton: false,
            confirmButtonText: "Join them now",
            closeOnConfirm: true
        }, function(){
            window.open(url,'_blank');
        });
        $('#inviteDialog').modal('hide');
    });
});

Roulette chat

VIEW DEMO

This demo explains how you can activate roulette mode for a room.
In this mode peers randomly connect with each other by clicking NEXT button.
This server side configuration snippet activates roulette mode:

'rooms'             => array(
    //...
    6   => array(
        'authAdapter'   => 'MgRTC\Session\AuthSimple',
        'roulette'         => true
    )

and client side:

//init video chat
$(document).ready(function(){
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://www.magnoliyan.com:8080?room=6'
    });
});

File transfer

VIEW DEMO

This demo explains how you can activate file transfer.
In this mode peers are able to send small files p2p if both sides use the same browser.
This server side configuration snippet activates file transfer:

'rooms'             => array(
    //...
    7   => array(
        'authAdapter'   => 'MgRTC\Session\AuthSimple',
        'file'         => true
    )

and client side:

//init video chat
$(document).ready(function(){
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://www.magnoliyan.com:8080?room=7',
        fileMaxSize: 512000 //500KB max file size
    });
});

Events API

VIEW DEMO

This example demonstrates how to leverage mgVideoChat javascript events api to customize the interface.

The key is to attach custom event handler on existing mgVideoChat plugin:

$(document).ready(function(){
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://' + wsDomain + ':' + wsPort + '?room=1'
    });
});
//on connections change
$('#mgVideoChat').mgVideoChat('on','connections',function(connections){
    console.log('[mgVideoChat.connections]fired',connections);
    if(!$.isEmptyObject(connections)){
        $("#supportWrapper .panel-title a").text("Support online");
    }else{
        $("#supportWrapper .panel-title a").text("Support offline");
    }
});

Please take a look at example's source code for more details.

Internationalization (I18N)

VIEW DEMO

This example demonstrates how to customize labels, messages etc. for wanted language.

All you have to do is update values of the key/value pairs in the global variable:

$.fn.mgVideoChat.translate = {    
    'Your browser does not support websocket.':  'Ihr Browser unterstützt keine WebSocket.',
    'Your browser does not support PeerConnections.':  'Ihr Browser unterstützt keine PeerConnections.',
    'Your browser does not support user media.':  'Ihr Browser unterstützt keine Benutzermedien.',
//...
}

You could do that inline, in the same file where you use mgVideoChat object, or in external script, but do it before the plugin is rendered.

All available translation keys you can find in the file

TURN server

VIEW DEMO

This example demonstrates how to integrate with third party SAAS provider www.turnservers.com in order to overcome media transport issue through NAT and firewalls.

Make sure to open account with turnservers.com first and obtain API key for your domains.

<script src="//api.turnservers.com/api.js?key=your_api_key"></script>
// This callback function will give us the iceServers:
window.turnserversDotComAPI.iceServers(function(data) {
    //console.log(data);
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'ws://' + wsDomain + ':' + wsPort + '?room=1',
        rtc: {
            pcConfig: {
                iceServers: data
            },
            pcConstraints: {"optional": [{"DtlsSrtpKeyAgreement": true}]},
            offerConstraints: {"optional": [], "mandatory": {}},
            mediaConstraints: {"audio": true, "video": true},
            sdpConstraints: {
                'mandatory': {
                    'OfferToReceiveAudio': true,
                    'OfferToReceiveVideo': true
                }
            },
            audio_receive_codec: 'opus/48000'
        }                        
    });
});

Please take a look at example's source code for more details.

Local TURN (coturn) server

VIEW DEMO

This example demonstrates how to integrate with your local coturn in order to overcome media transport issue through NAT and firewalls.

Please read first how to install and run coturn server.

// This callback function will give us the iceServers:
$.ajax({
    url: 'url_to_turn_rest_api/turn.php?username=defined_username',
    xhrFields: {
        withCredentials: false
    },
    success: function(data){
        console.log(data);
        //always add google's stun server
        var iceServers = [{'url':'stun:stun.l.google.com:19302'}];
        for(var i in data.uris){
            iceServers.push({
                'url': data.uris[i],
                'username': data.username,
                'credential': data.password
            });
        };
        console.log(iceServers);
        $('#mgVideoChat').mgVideoChat({
            wsURL: 'ws://' + wsDomain + ':' + wsPort + '?room=1',
            rtc: {
                pcConfig: {
                    'iceServers': iceServers
                },
                pcConstraints: {"optional": [{"DtlsSrtpKeyAgreement": true}]},
                offerConstraints: {"optional": [], "mandatory": {}},
                mediaConstraints: {"audio": true, "video": true},
                sdpConstraints: {
                    'mandatory': {
                        'OfferToReceiveAudio': true,
                        'OfferToReceiveVideo': true
                    }
                },
                audio_receive_codec: 'opus/48000'
            }                        
        });                        
    }
});

Please take a look at example's source code for more details.

Desktop sharing

VIEW DEMO

This example demonstrates ability to share your desktop with a peer. Desktop sharing comes with some limitations:

$(document).ready(function(){
    $('#mgVideoChat').mgVideoChat({
        wsURL: 'wss://' + wsDomain + '/wss/?room=1',
        chromeExtensionId: 'jfepeciommhoefhfacjdpcmnclekenag'
    });
});

Note that server must run over wss protocol. In order to achive this we enabled Apache's proxy_wstunnel. In our domain .conf we added:

SSLProxyEngine On
ProxyPass /wss/ ws://www.magnoliyan.com:8080/

It's important to include the same proxy path, e.g. /wss/ in wsURL parameter.

At the server since of our script we need to enable dekstop sharing for this room in config.php:

1   => array(
    //...
    'desktopShare'  => true
)

Google Chrome Extension

In order to make this work you will have to publish your custom branded Google Extension. A sharer is obliged to install this extension. The source code for the extension is provided at source/chrome-ext. You should update file manifest.json and icon.png with your custom info.
Make sure to update content_scripts/matches with a domain of your website where the script will be hosted.
After all is updated create zip file of all extension files and publish at Chrome WebStore Developer Dashboard. Learn more here.
Don't forget to update chromeExtensionId in javascript setup.

Please take a look at example's source code for more details.

This app uses the following Javascript libraries:

This app uses the following PHP library:

This app is inspired by the following projects:

Demo bootstrap themes by Bootswatch