├── README.md ├── app-call.php ├── app-functions.php ├── clean_older.php ├── images ├── avatar.png ├── chat.png └── no-picture.png ├── index.php ├── info.php ├── settings.php ├── snapshots ├── h5a-2w-c.png └── h5a-playback.jpg ├── sounds ├── buzz.mp3 ├── call.mp3 ├── error.mp3 ├── hello.mp3 ├── leave.mp3 ├── message.mp3 └── warning.mp3 ├── static ├── css │ ├── 2.4f53f970.chunk.css │ └── main.c308b1b0.chunk.css ├── js │ ├── 2.18e7dc77.chunk.js │ ├── main.75b83baf.chunk.js │ └── runtime-main.5e6501f7.js └── media │ ├── brand-icons.70150a2b.ttf │ ├── brand-icons.83494ca2.svg │ ├── brand-icons.85917bf2.eot │ ├── brand-icons.cac133c0.woff │ ├── brand-icons.dd746785.woff2 │ ├── flags.99f63ae7.png │ ├── icons.2f6dbd9f.eot │ ├── icons.9b4d14a5.ttf │ ├── icons.acc6b6bf.woff2 │ ├── icons.c8a5f741.svg │ ├── icons.e4efd599.woff │ ├── outline-icons.02428635.svg │ ├── outline-icons.6810be1d.eot │ ├── outline-icons.8a7914c9.woff │ ├── outline-icons.a3b4cd30.ttf │ └── outline-icons.a3f7358b.woff2 ├── tips ├── coins1.mp3 ├── coins2.mp3 ├── gift1.png ├── gift2.png ├── gift3.png ├── gift4.png ├── gift5.png └── register.mp3 ├── uploads └── integration.txt └── videos └── hamsterad.mp4 /README.md: -------------------------------------------------------------------------------- 1 | ## PHP-HTML5-Videochat / Live Streaming - Standalone PHP 2 | 3 | ### Live Demos for PHP Live Streaming / HTML5 Videochat : Broadcast & Playback Live Video 4 | 5 | [HTML5 Live Video Streaming using WowzaSE relay](https://demo.videowhisper.com/html5-videochat-php/) 6 | [HTML5 Live Video Streaming using P2P WebRTC](https://demo.videowhisper.com/vws-html5-livestreaming/) 7 | 8 | ![PHP Live Streaming Webcam](/snapshots/h5a-playback.jpg) 9 | 10 | Before installing, test the simple setup in the live demos above. 11 | 12 | This edition showcases streaming from 1 broadcaster to multiple viewers and chat. 13 | This plain php edition includes code and minimal scripts to embed a HTML5 Videochat app and test/showcase some features. This edition is for integrating/using application with own scripts/framework. 14 | For a complete implementation of advanced capabilities, see [Turnkey HTML5 Videochat Site](https://paidvideochat.com/html5-videochat/) edition, available as WordPress plugin with full php source. The turnkey site edition implements pay per minute videochat (group and private 2 way video calls) with membership, billing, advanced tools. 15 | 16 | ### Simple PHP Edition Features: Live Streaming: Broadcast & Playback 17 | 18 | - [x] Automatically create a room as broadcaster on access and show link to invite participants that will access as viewers 19 | - [x] Embed app to broadcast and playback live video using HTML5 WebRTC 20 | - [x] Simple implementation of signaling broadcast (to connect automatically) and text chat, using plain files 21 | 22 | ### Key Features for HTML5 Videochat / Live Streaming: Broadcast & Playback 23 | 24 | - [x] WebRTC 1 way to many live video streaming, in public lobby 25 | - [x] WebRTC relayed streaming (reliable and scalable to many clients from Wowza SE streaming server, independent of broadcaster upload connection) / P2P using VideoWhisper WebRTC 26 | - [x] select camera, microphone, resolution, bitrate 27 | - [x] screen sharing toggle, with microphone track mixed 28 | - [x] video/audio recorder, emoticons, mentions in text chat 29 | - [x] fullscreen for videochat interface or playback video 30 | - [x] adaptive target video bitrate (depending on cam resolution) and configuration in resolution change 31 | - [x] broadcasting/playback stats (open controls and stats should show in few seconds) 32 | - [x] dark mode / lights on: each user can toggle interface mode live at runtime, SFX (sound effects) 33 | - [x] translation and text change support 34 | - [ ] request private 2 way calls / shows from group chat 35 | - [ ] random videochat with Next button to move to different performer room 36 | - [ ] live wallet balance display (updates from tips and other transfers) 37 | - [ ] tips with multiple customizable options, gift images 38 | 39 | Warning: some of these features are not active/implemented in this simplified edition, but can be enabled as in turnkey site edition. 40 | 41 | ## Installation Instructions 42 | 43 | Before installing, make sure your hosting environment meets all [requirements](https://videowhisper.com/?p=Requirements) including the Wowza SE as HTML5 WebRTC streaming relay and/or the [VideoWhisper WebRTC signaling server](https://github.com/videowhisper/videowhisper-webrtc/). Production implementations should also involve Session Control for security and website integration (like list of live channels). 44 | For testing, get a free plan from [WebRTC Host: P2P](https://webrtchost.com/hosting-plans/#WebRTC-Only). 45 | 46 | 1. If you don't use a [turnkey webrtc relay streaming host](https://webrtchost.com/hosting-plans/), configure WebRTC + SSL with Wowza SE or the VideoWhisper WebRTC + STUN/TURN server. 47 | 2. Deploy files to your web installation location. (Example: yoursite.domain/html5-videochat/) 48 | 3. Fill your streaming settings in settings.php file 49 | 4. If you don't have SuPHP, enable write permissions (0777) for folder "uploads", required to save session and chat info. 50 | 51 | ## Plain PHP Edition Limitations 52 | 53 | - The plain php edition refers to minimal scripts for configuring and accessing videochat room, so developers can integrate with own scripts. 54 | - Plain php edition does not involve database and systems to manage members, rooms, billing. These depend on framework you want to integrate, plugins, database, member system. 55 | - Applications reads parameters, wallet balance and other data with ajax calls from framework/integration scripts (that need to be implemented depending on framework, database, user scripts). 56 | - A complete implementation of features is available for WordPress framework. See [Turnkey HTML5 Videochat Site](https://paidvideochat.com/html5-videochat/) edition, available as WordPress plugin with full php source. Includes user role management (performers/clients), pay per minute, integrates billing wallets. 57 | - Plain edition implements 1 way streaming and chat with broadcast / playback screens for broadcaster and other participants. Application supports but this edition does not implement signaling for requesting 2 way video calls or parameters and content for conference/collaborations. 58 | - Setup starts in demo mode, to prevent high resource usage by visitors. To enable and confirm full mode you need to fill application version in modeVersion parameter. [Consult VideoWhisper](https://consult.videowhisper.com) for assistance or a turnkey site setup. 59 | 60 | ## Main Integration Scripts 61 | 62 | - index.php embeds the html5 application: accessed directly creates a room and shows room link to invite others 63 | - app-call.php is called by application to retrieve parameters, interact with web server, update status and chat (ajax calls) 64 | - app-functions.php functions implementing features for app-call.php , including translated texts, app settings 65 | - settings.php settings and options, including streaming settings and url for calls (when integrating with own framework) 66 | 67 | Scripts also contain comments for clarifications/suggestions. 68 | 69 | This is a simple setup showcasing easy app deployment and integration with other PHP scripts. 70 | For a quick setup, see [VideoWhisper Turnkey Stream Hosting Plans](https://webrtchost.com/hosting-plans/) that include requirements for all features and free installation. 71 | 72 | ### VideoWhisper HTML5 Project Demos 73 | 74 | - [Video Call PHP / HTML5 Videochat on Wowza SE](https://demo.videowhisper.com/videocall-html5-videochat-php/) 75 | - [Video Call PHP / HTML5 Videochat on VideoWhisper WebRTC](https://demo.videowhisper.com/p2p-html5-videocall/) 76 | - [Live Streaming PHP / HTML5 Videochat on Wowza SE](https://demo.videowhisper.com/html5-videochat-php/) 77 | - [Live Streaming PHP / HTML5 Videochat on VideoWhisper WebRTC](https://demo.videowhisper.com/vws-html5-livestreaming/) 78 | - [Cam/Mic Recorder HTML5 - Standalone](https://demo.videowhisper.com/cam-recorder-html5-video-audio/) 79 | - [PaidVideochat Turnkey Site](https://paidvideochat.com/demo/) 80 | 81 | ### VideoWhisper HTML5 Project Downloads 82 | 83 | - [Video Call - HTML5 Videochat - GitHub](https://github.com/videowhisper/VideoCall-HTML5-Videochat-PHP) 84 | - [Live Streaming - HTML5 Videochat - GitHub](https://github.com/videowhisper/HTML5-Videochat-PHP) 85 | - [Cam/Mic Recorder HTML5 - GitHub](https://github.com/videowhisper/Cam-Recorder-HTML5-Video-Audio) 86 | - [PaidVideochat Turnkey Site - WordPress](https://wordpress.org/plugins/ppv-live-webcams/) 87 | - [Video Calls & Random Chat Turnkey Site - WordPress](https://wordpress.org/plugins/webcam-2way-videochat/) 88 | - [WebRTC Signaling Server](https://github.com/videowhisper/videowhisper-webrtc/) 89 | 90 | [Consult VideoWhisper](https://consult.videowhisper.com) for commercial services like turnkey site platforms, compatible hosting, custom development services. 91 | -------------------------------------------------------------------------------- /app-call.php: -------------------------------------------------------------------------------- 1 | intval($userID), 40 | 'name'=> (($userID>10000)?'Performer':'User') . $userID, 41 | 'sessionID'=> intval($sessionID), 42 | 'loggedIn' => true, 43 | 'balance' => 100, 44 | 'avatar' => VW_H5V_URL .'images/avatar.png', 45 | ]; 46 | 47 | 48 | //on login check if any private request was active to restore 49 | //return private room/session if active, depending on integration 50 | $response['room'] = appPublicRoom($roomID, $userID, $options, 'Login success!'); 51 | 52 | //config params, const 53 | $response['config'] = [ 54 | 'serverType' => $options['serverType'], 55 | 'vwsSocket' => $options['vwsSocket'], 56 | 'vwsToken' => $options['vwsToken'], 57 | 58 | 'wss' => $options['wsURLWebRTC'], 59 | 'application' => $options['applicationWebRTC'], 60 | 61 | 'videoCodec' => $options['webrtcVideoCodec'], 62 | 'videoBitrate' => $options['webrtcVideoBitrate'], 63 | 'audioBitrate' => $options['webrtcAudioBitrate'], 64 | 'audioCodec' => $options['webrtcAudioCodec'], 65 | 66 | 'snapshotInterval' => 180, 67 | 'snapshotDisable' => true, 68 | 69 | // 'autoBroadcast' => false, 70 | 'actionFullscreen' => true, 71 | 'actionFullpage' => false, 72 | 73 | 'serverURL' => VW_H5V_CALL, 74 | 'modeVersion' => '', 75 | 76 | ]; 77 | 78 | $response['config']['text'] = appText(); //translations 79 | $response['config']['sfx'] = appSfx(); 80 | 81 | $response['config']['exitURL'] = VW_H5V_URL . 'info.php?i=exit'; 82 | $response['config']['balanceURL'] = VW_H5V_URL . 'info.php?i=wallet' ; 83 | 84 | //pass app setup config parameters 85 | if (is_array($options['appSetup'])) 86 | if (array_key_exists('Config', $options['appSetup'])) 87 | if (is_array($options['appSetup']['Config'])) 88 | foreach ($options['appSetup']['Config'] as $key => $value) 89 | $response['config'][$key] = $value; 90 | 91 | 92 | if (VW_DEVMODE) 93 | { 94 | // $response['config']['cameraAutoBroadcast'] = '0'; 95 | // $response['config']['videoAutoPlay '] = '0'; 96 | 97 | } 98 | 99 | //if (!$isPerformer) $response['config']['cameraAutoBroadcast'] = '0'; 100 | 101 | $response['config']['loaded'] = true; 102 | 103 | } 104 | //end: task==login 105 | 106 | 107 | //room 108 | $roomName = 'Room' . $roomID; 109 | $userName = (($userID>10000)?'Performer':'User') . $userID; 110 | $ztime = time(); 111 | 112 | //create/update session in room session list 113 | 114 | $sessions = arrayLoad($roomID . '_sessions'); 115 | if (array_key_exists($sessionID, $sessions)) $session = $sessions[$sessionID]; 116 | else $session = array( 117 | 'id' => $sessionID, 118 | 'uid' => $userID, 119 | 'sdate' => time(), 120 | 'madeBy' => 'access', 121 | ); 122 | 123 | $session['edate'] = time(); 124 | 125 | $sessions[$sessionID] = $session; 126 | 127 | varSave($roomID . '_sessions', $sessions); 128 | 129 | 130 | //update private session if in private mode 131 | 132 | $needUpdate = array(); 133 | 134 | //process app task (other than login) 135 | switch ($task) 136 | { 137 | case 'snapshot': 138 | break; 139 | 140 | case 'login': 141 | break; 142 | 143 | case 'tick': 144 | break; 145 | 146 | case 'options': 147 | break; 148 | 149 | case 'update': 150 | //something changed - let everybody know : update room 151 | $update = filter_var($_POST['update'], FILTER_SANITIZE_STRING); 152 | 153 | 154 | $room = arrayLoad($roomID . '_room'); 155 | $room['updated_' . $update] = time(); 156 | 157 | varSave($roomID . '_room', $room); 158 | $needUpdate[$update] = 1; 159 | break; 160 | 161 | case 'media': 162 | //notify user media (streaming) updates 163 | 164 | $connected = ($_POST['connected'] == 'true'?true:false); 165 | 166 | if ($session['meta']) 167 | if (!is_array($userMeta = unserialize($session['meta']))) $userMeta = array(); 168 | 169 | $userMeta['connected'] = $connected; 170 | $userMeta['connectedUpdate'] = time(); 171 | 172 | $userMetaS = serialize($userMeta); 173 | 174 | $sessions = arrayLoad($roomID . '_sessions'); 175 | if (!array_key_exists($sessionID, $sessions)) $sessions[$sessionID] = array('id' =>$sessionID, 'sdate' => time(), 176 | 'madeBy'=>'media'); 177 | $sessions[$sessionID]['meta'] = $userMetaS; 178 | 179 | varSave($roomID . '_sessions', $sessions); 180 | break; 181 | 182 | 183 | case 'message': 184 | 185 | $message = $_POST['message']; //array 186 | 187 | if ($message) 188 | { 189 | $response['message'] = $message; 190 | } 191 | 192 | $messageText = filter_var( $message['text'], FILTER_SANITIZE_STRING); 193 | $messageUser = filter_var( $message['userName'], FILTER_SANITIZE_STRING); 194 | $messageUserAvatar = filter_var( $message['userAvatar'], FILTER_SANITIZE_URL); 195 | 196 | $meta = array( 197 | 'notification'=> filter_var($message['notification'],FILTER_SANITIZE_STRING), 198 | 'userAvatar' => $messageUserAvatar, 199 | 'mentionMessage' => intval($message['mentionMessage']), 200 | 'mentionUser'=> filter_var($message['mentionUser'],FILTER_SANITIZE_STRING), 201 | ); 202 | $metaS = serialize($meta); 203 | 204 | if (!$privateUID) $privateUID = 0; //public room 205 | 206 | $messages = arrayLoad($roomID . '_messages'); 207 | $ix = count($messages)+1; 208 | 209 | 210 | $messageNew = array( 211 | 'id' => $ix, 212 | 'username' => $messageUser, 213 | 'room' => $roomName, 214 | 'room_id' => $roomID, 215 | 'message' => $messageText, 216 | 'mdate' => $ztime, 217 | 'type' => 2, 218 | 'user_id' => $userID, 219 | 'meta' => $metaS, 220 | 'private_uid' => $privateUID 221 | ); 222 | 223 | 224 | $messages[$ix] = $messageNew; 225 | $response['messageNew'] = $messageNew; 226 | 227 | varSave($roomID . '_messages', $messages); 228 | 229 | break; 230 | 231 | 232 | case 'recorder_upload': 233 | 234 | 235 | if (!$roomName) appFail('No room for recording.'); 236 | if (strstr($filename, ".php")) appFail('Bad uploader!'); 237 | 238 | 239 | $mode = $_POST['mode']; // video/audio 240 | $scenario = $_POST['scenario']; // chat/standalone 241 | 242 | if (!$privateUID) $privateUID = 0; //public room 243 | 244 | //generate same private room folder for both users 245 | if ($privateUID) 246 | { 247 | if ($isPerformer) $proom = $userID . "_" . $privateUID; //performer id first 248 | else $proom = $privateUID ."_". $userID; 249 | } 250 | 251 | $destination = 'uploads'; 252 | if (!file_exists($destination)) mkdir($destination); 253 | 254 | $destination.="/$roomName"; 255 | if (!file_exists($destination)) mkdir($destination); 256 | 257 | if ($proom) 258 | { 259 | $destination.="/$proom"; 260 | if (!file_exists($destination)) mkdir($destination); 261 | } 262 | 263 | $response['_FILES'] = $_FILES; 264 | 265 | 266 | $allowed = array('mp3', 'ogg', 'opus', 'mp4', 'webm', 'mkv'); 267 | 268 | $uploads = 0; 269 | $filename = ''; 270 | 271 | if ($_FILES) if (is_array($_FILES)) 272 | foreach ($_FILES as $ix => $file) 273 | { 274 | 275 | $filename = filter_var( $file['name'], FILTER_SANITIZE_STRING); 276 | 277 | $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); 278 | $response['uploadRecLastExt'] = $ext; 279 | $response['uploadRecLastF'] = $filename; 280 | 281 | $filepath = $destination . '/' . $filename; 282 | 283 | if (in_array($ext, $allowed)) 284 | if (file_exists($file['tmp_name'])) 285 | { 286 | move_uploaded_file($file['tmp_name'], $filepath); 287 | $response['uploadRecLast'] = $destination . $filename; 288 | $uploads++; 289 | } 290 | } 291 | 292 | $response['uploadCount'] = $uploads; 293 | 294 | 295 | if (!file_exists($filepath)) 296 | { 297 | $response['warning'] = 'Recording upload failed!'; 298 | } 299 | 300 | 301 | 302 | if ( !$response['warning'] && $scenario == 'chat' ) 303 | { 304 | $url = path2url($filepath); 305 | 306 | $response['recordingUploadSize'] = filesize($filepath); 307 | $response['recordingUploadURL'] = $url; 308 | 309 | $messageText = ''; 310 | $messageUser = $userName; 311 | $userAvatar = VW_H5V_URL .'images/avatar.png'; 312 | $messageUserAvatar = $userAvatar; 313 | 314 | $meta = array( 315 | 'userAvatar' => $messageUserAvatar, 316 | ); 317 | 318 | if ($mode == 'video') $meta['video']= $url; 319 | else $meta['audio']= $url; 320 | 321 | $metaS = serialize($meta); 322 | 323 | 324 | $messages = arrayLoad($roomID . '_messages'); 325 | $ix = count($messages)+1; 326 | 327 | $messageNew = array( 328 | 'id' => $ix, 329 | 'username' => $messageUser, 330 | 'room' => $roomName, 331 | 'room_id' => $roomID, 332 | 'message' => $messageText, 333 | 'mdate' => $ztime, 334 | 'type' => 2, 335 | 'user_id' => $userID, 336 | 'meta' => $metaS, 337 | 'private_uid' => $privateUID 338 | ); 339 | $messages[$ix] = $messageNew; 340 | 341 | varSave($roomID . '_messages', $messages); 342 | 343 | $response['messageNew'] = $messageNew; 344 | 345 | //also update chat log file 346 | /* 347 | if ($roomName) 348 | { 349 | $messageText = strip_tags($messageText, '

'); 350 | $messageText = date("F j, Y, g:i a", $ztime) . " $userName: $messageText "; 351 | $day=date("y-M-j", time()); 352 | $dfile = fopen($destination . "/Log$day.html", "a"); 353 | fputs($dfile, $messageText."
"); 354 | fclose($dfile); 355 | } 356 | */ 357 | } 358 | break; 359 | 360 | default: 361 | $response['warning'] = 'Not implemented in this integration: ' . $task; 362 | 363 | } 364 | 365 | 366 | //update time 367 | $lastMessage = intval($_POST['lastMessage'] ?? 0); 368 | $lastMessageID = intval($_POST['lastMessageID'] ?? 0); 369 | 370 | //retrieve only messages since user came online / updated 371 | $sdate = 0; 372 | if ($session) $sdate = $session['sdate']; 373 | $startTime = max($sdate, $lastMessage); 374 | 375 | $response['startTime'] = $startTime; 376 | 377 | 378 | //!messages 379 | 380 | $messages = arrayLoad($roomID . '_messages'); 381 | 382 | //clean old chat logs 383 | $closeTime = time() - 900; //only keep for 15min 384 | foreach ($messages as $key => $message) if ($message['mdate'] < $closeTime) unset($messages[$key]); 385 | varSave($roomID . '_messages', $messages); 386 | 387 | 388 | 389 | //sort by key (time) 390 | ksort($messages); 391 | 392 | 393 | $items = array(); 394 | $idMax = 0; 395 | 396 | 397 | foreach ($messages as $key => $message) 398 | { 399 | $show = 1; 400 | 401 | //chat message or own notification (type 3) 402 | if ($message['type'] > 3) $show = 0; 403 | if ($message['type'] == 3 && ($message['user_id'] != $userID && $message['username'] != $userName) ) $show = 0; 404 | 405 | 406 | if (!$privateUID) if ($message['private_uid'] != 0) $show = 0; //private messages only in private 407 | 408 | if ($privateUID) 409 | { 410 | if ($message['private_uid'] != $privateUID) $show = 0; //not in this private 411 | if ($message['user_id'] != $userID && $message['user_id'] != $privateUID) $show = 0; //not for user or other 412 | } 413 | 414 | 415 | //enable this to show only messages after entering $startTime: 416 | //if ($message['mdate'] < $startTime || $message['mdate'] > $ztime) $show = 0; 417 | 418 | if ($message['id'] <= $lastMessageID) $show = 0; 419 | 420 | $idMax = 0; 421 | if ($show) 422 | { 423 | $item = []; 424 | 425 | $item['ID'] = intval($message['id']); 426 | if ($item['ID']>$idMax) $idMax = $item['ID']; 427 | 428 | $item['userName'] = $message['username']; 429 | $item['userID'] = intval($message['user_id']); 430 | 431 | $item['text'] = $message['message']; 432 | $item['time'] = intval($message['mdate'] * 1000); //time in ms for js 433 | 434 | $item['userAvatar'] = VW_H5V_URL .'images/avatar.png'; 435 | 436 | //meta 437 | if ($message['meta']) 438 | { 439 | $meta = unserialize($message['meta']); 440 | foreach ($meta as $key=>$value) $item[$key] = $value; 441 | 442 | $item['notification'] = (isset($meta['notification']) && $meta['notification'] == 'true' ? true : false ); 443 | } 444 | 445 | if ($message['type'] == 3) $item['notification'] = true; 446 | 447 | $items[] = $item; 448 | } 449 | 450 | } 451 | 452 | 453 | $response['messages'] = $items; //messages list 454 | 455 | $response['timestamp'] = $ztime; //update time 456 | ///update message 457 | 458 | $response['lastMessageID'] = $idMax; 459 | 460 | $response['roomUpdate']['users'] = appRoomUsers($roomID, $options); 461 | 462 | 463 | //send response to app 464 | echo json_encode($response); 465 | -------------------------------------------------------------------------------- /app-functions.php: -------------------------------------------------------------------------------- 1 | $base . 'message.mp3', 70 | 'hello' => $base . 'hello.mp3', 71 | 'leave' => $base . 'leave.mp3', 72 | 'call' => $base . 'call.mp3', 73 | 'warning' => $base . 'warning.mp3', 74 | 'error' => $base . 'error.mp3', 75 | 'buzz' => $base . 'buzz.mp3', 76 | ); 77 | } 78 | 79 | function appText() 80 | { 81 | //implement translations 82 | 83 | //returns texts 84 | return array( 85 | 'Send' => __('Send', 'ppv-live-webcams'), 86 | 'Type your message' => __('Type your message', 'ppv-live-webcams'), 87 | 88 | 'Chat' => __('Chat', 'ppv-live-webcams'), 89 | 'Camera' => __('Camera', 'ppv-live-webcams'), 90 | 'Users' => __('Users', 'ppv-live-webcams'), 91 | 'Options' => __('Options', 'ppv-live-webcams'), 92 | 'Files' => __('Files', 'ppv-live-webcams'), 93 | 'Presentation' => __('Presentation', 'ppv-live-webcams'), 94 | 95 | 'Tap for Sound' => __('Tap for Sound', 'ppv-live-webcams'), 96 | 'Enable Audio' => __('Enable Audio', 'ppv-live-webcams'), 97 | 'Mute' => __('Mute', 'ppv-live-webcams'), 98 | 'Reload' => __('Reload', 'ppv-live-webcams'), 99 | 100 | 'Broadcast' => __('Broadcast', 'ppv-live-webcams'), 101 | 'Stop Broadcast' => __('Stop Broadcast', 'ppv-live-webcams'), 102 | 'Make a selection to start!' => __('Make a selection to start!', 'ppv-live-webcams'), 103 | 104 | 'Lights On' => __('Lights On', 'ppv-live-webcams'), 105 | 'Dark Mode' => __('Dark Mode', 'ppv-live-webcams'), 106 | 'Enter Fullscreen' => __('Enter Fullscreen', 'ppv-live-webcams'), 107 | 'Exit Fullscreen' => __('Exit Fullscreen', 'ppv-live-webcams'), 108 | 109 | 'Site Menu' => __('Site Menu', 'ppv-live-webcams'), 110 | 111 | 'Request Private' => __('Request Private', 'ppv-live-webcams'), 112 | 'Request Private 2 Way Videochat Show' => __('Request Private 2 Way Videochat Show', 'ppv-live-webcams'), 113 | 'Performer Disabled Private Requests' => __('Performer Disabled Private Requests', 'ppv-live-webcams'), 114 | 'Performer is Busy in Private' => __('Performer is Busy in Private', 'ppv-live-webcams'), 115 | 'Performer is Not Online' => __('Performer is Not Online', 'ppv-live-webcams'), 116 | 'Nevermind' => __('Nevermind', 'ppv-live-webcams'), 117 | 'Accept' => __('Accept', 'ppv-live-webcams'), 118 | 'Decline' => __('Decline', 'ppv-live-webcams'), 119 | 'Close Private' => __('Close Private', 'ppv-live-webcams'), 120 | 121 | 'Next' => __('Next', 'ppv-live-webcams'), 122 | 'Next: Random Videochat Room' => __('Next: Random Videochat Room', 'ppv-live-webcams'), 123 | 124 | 'Name' => __('Name', 'ppv-live-webcams'), 125 | 'Size' => __('Size', 'ppv-live-webcams'), 126 | 'Age' => __('Age', 'ppv-live-webcams'), 127 | 'Upload: Drag and drop files here, or click to select files' => __('Upload: Drag and drop files here, or click to select files', 'ppv-live-webcams'), 128 | 'Uploading. Please wait...' => __('Uploading. Please wait...', 'ppv-live-webcams'), 129 | 'Open' => __('Open', 'ppv-live-webcams'), 130 | 'Delete' => __('Delete', 'ppv-live-webcams'), 131 | 132 | 'Media Displayed' => __('Media Displayed', 'ppv-live-webcams'), 133 | 'Remove' => __('Remove', 'ppv-live-webcams'), 134 | 'Default' => __('Default', 'ppv-live-webcams'), 135 | 'Empty' => __('Empty', 'ppv-live-webcams'), 136 | 137 | 'Profile' => __('Profile', 'ppv-live-webcams'), 138 | 'Show' => __('Show', 'ppv-live-webcams'), 139 | 140 | 'Private Call' => __('Private Call', 'ppv-live-webcams'), 141 | 'Exit' => __('Exit', 'ppv-live-webcams'), 142 | 143 | 'External Broadcast' => __('External Broadcast', 'ppv-live-webcams'), 144 | 'Broadcast with external apps for advanced compositions, scenes, effects or reliability compared to web based interface and protocols. External broadcasts have higher latency and improved capacity, reliability specific to HLS delivery method. New broadcasts show in about 10 seconds and unavailability updates after 1 minute.' => __('Broadcast with external apps for advanced compositions, scenes, effects or reliability compared to web based interface and protocols. External broadcasts have higher latency and improved capacity, reliability specific to HLS delivery method. New broadcasts show in about 10 seconds and unavailability updates after 1 minute.', 'ppv-live-webcams'), 145 | ); 146 | } 147 | 148 | 149 | function appRoomUsers($roomID, $options) 150 | { 151 | 152 | $sessions = arrayLoad($roomID . '_sessions'); 153 | 154 | foreach ($sessions as $key => $session) 155 | { 156 | if (!isset($session['meta']) || !is_array( $userMeta = unserialize($session['meta']) ) ) $userMeta = array(); 157 | 158 | $item = []; 159 | $item['userID'] = intval($session['uid']); 160 | $item['userName'] = $session['username'] ?? ''; 161 | if (!$item['userName']) $item['userName'] = '#' . $session['uid']; 162 | 163 | $item['sdate'] = intval($session['sdate']); 164 | $item['meta'] = $userMeta; 165 | $item['updated'] = intval($session['edate']); 166 | $item['avatar'] = VW_H5V_URL .'images/avatar.png'; 167 | $item['url'] = 'https://videowhisper.com/tickets_submit.php'; 168 | 169 | $items[intval($session['uid'])] = $item; 170 | } 171 | 172 | return $items; 173 | } 174 | 175 | 176 | function appTipOptions($options) 177 | { 178 | 179 | $tipOptions = stripslashes($options['tipOptions']); 180 | if ($tipOptions) 181 | { 182 | $p = xml_parser_create(); 183 | xml_parse_into_struct($p, trim($tipOptions), $vals, $index); 184 | $error = xml_get_error_code($p); 185 | xml_parser_free($p); 186 | 187 | if (is_array($vals)) return $vals; 188 | } 189 | 190 | return array(); 191 | 192 | } 193 | 194 | 195 | function appStream($userID, $roomID, $options) 196 | { 197 | $key = $options['webKey'] . $roomID; //a secret key to implement stream verification 198 | 199 | return 'stream' . $userID . '?channel_id=' . $roomID . '&userID=' . urlencode($userID) . '&key=' . urlencode($key) . '&ip=' . urlencode($_SERVER['REMOTE_ADDR']) . '&transcoding=0&room=Room' . $roomID. '&privateUID=0'; 200 | 201 | } 202 | 203 | function appPublicRoom($roomID, $userID, $options, $welcome ='') 204 | { 205 | //public room parameters, specific for this user 206 | //depends on integration 207 | 208 | $room = array(); 209 | 210 | $room['ID'] = $roomID; 211 | $room['name'] = 'Room' . $roomID; 212 | 213 | $room['performer'] = 'Performer' . (10000+$roomID); 214 | $room['performerID'] = (10000+$roomID); 215 | 216 | $collaboration = $options['collaboration']; 217 | if (VW_DEVMODE && VW_DEVMODE_COLLABORATION) $collaboration = true; 218 | 219 | $isPerformer = ($userID == (10000+$roomID)); 220 | 221 | //screen 222 | if ($isPerformer) $roomScreen = 'BroadcastScreen'; 223 | else $roomScreen = 'PlaybackScreen'; 224 | if ($collaboration) $roomScreen = 'CollaborationScreen'; 225 | $room['screen'] = $roomScreen; 226 | 227 | $room['streamBroadcast'] = appStream($userID, $roomID, $options); 228 | 229 | $room['streamUID'] = intval($room['performerID']); 230 | $room['streamPlayback'] = appStream($room['performerID'], $roomID, $options); 231 | 232 | //$room['actionPrivate'] = !$isPerformer; 233 | $room['actionPrivateClose'] = false; 234 | $room['privateUID'] = 0; 235 | 236 | $room['actionID'] = 0; 237 | 238 | $room['welcome'] = ' 💬 ' . sprintf('Welcome to public room "%s", user #%s!', $room['name'] , $userID); 239 | $room['welcomeImage'] = VW_H5V_URL . 'images/chat.png'; 240 | 241 | 242 | 243 | if ($isPerformer) //member: performer 244 | { 245 | $room['welcome'] .= "\n 📡 ". 'You are broadcaster (room owner/performer). Use best network available if you have the option: 5GHz on WiFi instead of 2.4 GHz, LTE/4G on mobile instead of 3G, wired instead of wireless. '; 246 | } 247 | else //member: client 248 | { 249 | $room['welcome'] .= "\n 👤 ".'You are invited participant.'; 250 | } 251 | 252 | 253 | 254 | 255 | //if ($options['videochatNext']) if (!$isPerformer) $room['next'] = true; 256 | 257 | if ($welcome) $room['welcome'] .= "\n" . $welcome; 258 | 259 | //configure tipping options for clients 260 | $room['tips'] = false; 261 | if ($options['tips']) 262 | if (!$isPerformer) 263 | { 264 | $tipOptions = appTipOptions($options); 265 | if (count($tipOptions)) 266 | { 267 | $room['tipOptions'] = $tipOptions; 268 | $room['tips'] = true; 269 | $room['tipsURL'] = VW_H5V_URL . 'tips/'; 270 | } 271 | } 272 | 273 | //demo goal 274 | $room['welcome'] .= "\n 🎁 " . 'Current gifts goal' .': '. 'Demo Goal'; 275 | $room['welcome'] .= "\n - " . 'Goal description' .': ' . 'Chat can display goals that can be achieved with gifts/donations.'; 276 | $room['welcomeProgressValue'] = 8; 277 | $room['welcomeProgressTotal'] = 10; 278 | $room['welcomeProgressDetails'] = 'Demo Goal'; 279 | 280 | //offline snapshot (poster) and video 281 | $room['snapshot'] = VW_H5V_URL . 'images/no-picture.png'; 282 | $room['videoOffline'] = VW_H5V_URL . 'videos/hamsterad.mp4';; 283 | 284 | return $room; 285 | } 286 | 287 | -------------------------------------------------------------------------------- /clean_older.php: -------------------------------------------------------------------------------- 1 | $old) return @rmdir($path); 28 | else return -1; 29 | } else { 30 | if ($delete_this) if (time()-filemtime($path)> $old) return @unlink($path); 31 | else return -1; 32 | } 33 | return -1; 34 | } 35 | 36 | function cleanUp($dir) 37 | { 38 | global $old; 39 | 40 | echo "

Cleaning up old files ..."; 41 | $k=0; 42 | $handle=opendir($dir); 43 | while (($file = readdir($handle))!==false) 44 | { 45 | if (($file != ".") && ($file != "..")) 46 | { 47 | if (is_dir("$dir/" . $file)) deltree($dir."/".$file); 48 | elseif (time()-filemtime("$dir/" . $file)> $old) @unlink("$dir/" . $file); 49 | $k++; 50 | 51 | if ($k%50==0) 52 | { 53 | echo " ."; 54 | flush(); 55 | } 56 | } 57 | } 58 | closedir($handle); 59 | echo "
"; 60 | } 61 | 62 | cleanUp('uploads'); 63 | ?> -------------------------------------------------------------------------------- /images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/images/avatar.png -------------------------------------------------------------------------------- /images/chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/images/chat.png -------------------------------------------------------------------------------- /images/no-picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/images/no-picture.png -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 34 | 35 |
36 | 37 | HTMLCODE; 38 | 39 | //app requires semantic ui 40 | $headCode = ''; 41 | 42 | //app css & js 43 | $CSSfiles = scandir(dirname( __FILE__ ) . '/static/css/'); 44 | foreach($CSSfiles as $filename) 45 | if (strpos($filename,'.css')&&!strpos($filename,'.css.map')) 46 | $headCode .= ''; 47 | 48 | $JSfiles = scandir(dirname( __FILE__ ) . '/static/js/'); 49 | foreach ($JSfiles as $filename) 50 | if ( strpos($filename,'.js') && !strpos($filename,'.js.map')) 51 | $bodyCode .= ''; 52 | 53 | 54 | //room link 55 | $roomURL = $_SERVER['REQUEST_SCHEME'] .'://'. $_SERVER['HTTP_HOST'] . explode('?', $_SERVER['REQUEST_URI'], 2)[0] . '?r=' . $roomID; 56 | $bodyCode .= '
'; 59 | ?> 60 | 61 | 62 | 63 | 64 | 65 |

HTML5 Videochat - Plain PHP / Live Streaming: Broadcast & Playback

This setup implements the room lobby with simple live video streaming (from performer to participants) and text chat. This is a simple embedding preview edition, with simple scripts to embed app and showcase few features. 66 | 67 |
+ Official Live Demo for Live Streaming / HTML5 Videochat Standalone PHP: Live Streaming on WowzaSE | Live Streaming on VideoWhisper WebRTC 68 |
+ Download from GitHub: Live Streaming / HTML5 Videochat PHP 69 |
+ All Plain PHP Demos: P2P Video Call | Video Call on Wowza SE | Live Streaming on Wowza SE | Live Streaming on VideoWhisper WebRTC | Cam/Mic Recorder 70 |
+ Server GitHub: VideoWhisper WebRTC signaling server (NodeJS, supports using STUN/TURN serverlike CoTURN) 71 |
+ For testing, get a Free plan from WebRTC Host: P2P. 72 |
+ By default application starts in demo mode, for free testing with low resources by site visitors. 73 |
+ Technical support & turnkey site plans: Consult VideoWhisper 74 |
+ Turnkey Cam Site Solution as WP plugins: Turnkey HTML5 Videochat Site. 75 |
76 | 77 | 80 | -------------------------------------------------------------------------------- /info.php: -------------------------------------------------------------------------------- 1 |

Integration Info

This functionality depends on site framework, features, database, user and billing systems.
2 | -------------------------------------------------------------------------------- /settings.php: -------------------------------------------------------------------------------- 1 | 'videowhisper', // videowhisper/wowza 9 | 'vwsSocket' => 'wss://[videowhisper-server]:[port]', 10 | 'vwsToken' => 'server-token', 11 | 12 | 'wsURLWebRTC' => 'wss://-server-id-.streamlock.net:1937/webrtc-session.json', 13 | 'applicationWebRTC' => '-application-name-', 14 | 15 | 'webrtcVideoCodec' =>'VP8', 16 | 'webrtcAudioCodec' =>'opus', 17 | 18 | 'webrtcVideoBitrate' => 500, // demo mode limited to 750kbps, 480p 19 | 'webrtcAudioBitrate' => 32, 20 | 21 | 'appSetup' => unserialize('a:3:{s:6:"Config";a:12:{s:8:"darkMode";s:0:"";s:7:"tabMenu";s:4:"icon";s:19:"cameraAutoBroadcast";s:1:"1";s:22:"cameraAutoBroadcastAll";s:1:"1";s:14:"cameraControls";s:1:"1";s:13:"videoAutoPlay";s:0:"";s:16:"resolutionHeight";s:3:"360";s:7:"bitrate";s:3:"500";s:19:"maxResolutionHeight";s:4:"1080";s:10:"maxBitrate";s:4:"3500";s:12:"timeInterval";s:4:"5000";s:15:"recorderMaxTime";s:3:"300";}s:4:"Room";a:15:{s:16:"requests_disable";s:0:"";s:12:"room_private";s:0:"";s:10:"room_audio";s:0:"";s:9:"room_text";s:0:"";s:15:"room_conference";s:0:"";s:15:"conference_auto";s:1:"1";s:10:"room_slots";s:1:"4";s:19:"vw_presentationMode";s:0:"";s:10:"calls_only";s:0:"";s:14:"group_disabled";s:0:"";s:5:"party";s:0:"";s:14:"party_reserved";s:1:"0";s:13:"stream_record";s:0:"";s:21:"stream_record_private";s:0:"";s:17:"stream_record_all";s:0:"";}s:4:"User";a:5:{s:7:"h5v_sfx";s:0:"";s:8:"h5v_dark";s:0:"";s:9:"h5v_audio";s:0:"";s:10:"h5v_reveal";s:0:"";s:17:"h5v_reveal_warmup";s:2:"30";}}'), 22 | 23 | 'collaboration' => 0, 24 | 'webKey' => 'VideoWhisper', 25 | 26 | 'videochatNext' =>1, 27 | 28 | 'tips' => 1, 29 | 'tipOptions' => ' 30 | 31 | 32 | 33 | 34 | 35 | 36 | ' 37 | ); 38 | 39 | //installation url & integration calls 40 | const VW_H5V_URL = ''; //leave blank if loading from same folder, ex: https://yoursite.com/html5-videochat/ 41 | const VW_H5V_CALL = VW_H5V_URL . 'app-call.php?v=1'; 42 | 43 | //development & debugging 44 | define('VW_DEVMODE', 1); 45 | define('VW_DEVMODE_COLLABORATION', 0); 46 | define('VW_DEVMODE_CLIENT', 0); 47 | 48 | if (VW_DEVMODE) 49 | { 50 | ini_set('display_errors', 1); 51 | error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT); 52 | } -------------------------------------------------------------------------------- /snapshots/h5a-2w-c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/snapshots/h5a-2w-c.png -------------------------------------------------------------------------------- /snapshots/h5a-playback.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/snapshots/h5a-playback.jpg -------------------------------------------------------------------------------- /sounds/buzz.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/sounds/buzz.mp3 -------------------------------------------------------------------------------- /sounds/call.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/sounds/call.mp3 -------------------------------------------------------------------------------- /sounds/error.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/sounds/error.mp3 -------------------------------------------------------------------------------- /sounds/hello.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/sounds/hello.mp3 -------------------------------------------------------------------------------- /sounds/leave.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/sounds/leave.mp3 -------------------------------------------------------------------------------- /sounds/message.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/sounds/message.mp3 -------------------------------------------------------------------------------- /sounds/warning.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/sounds/warning.mp3 -------------------------------------------------------------------------------- /static/css/2.4f53f970.chunk.css: -------------------------------------------------------------------------------- 1 | .emoji-mart,.emoji-mart *{box-sizing:border-box;line-height:1.15}.emoji-mart{font-family:-apple-system,BlinkMacSystemFont,"Helvetica Neue",sans-serif;font-size:16px;display:inline-block;color:#222427;border:1px solid #d9d9d9;border-radius:5px;background:#fff}.emoji-mart .emoji-mart-emoji{padding:6px}.emoji-mart-bar{border:0 solid #d9d9d9}.emoji-mart-bar:first-child{border-bottom-width:1px;border-top-left-radius:5px;border-top-right-radius:5px}.emoji-mart-bar:last-child{border-top-width:1px;border-bottom-left-radius:5px;border-bottom-right-radius:5px}.emoji-mart-anchors{display:flex;flex-direction:row;justify-content:space-between;padding:0 6px;line-height:0}.emoji-mart-anchor{position:relative;display:block;flex:1 1 auto;color:#858585;text-align:center;padding:12px 4px;overflow:hidden;transition:color .1s ease-out;margin:0;box-shadow:none;background:none;border:none}.emoji-mart-anchor:focus{outline:0}.emoji-mart-anchor-selected,.emoji-mart-anchor:focus,.emoji-mart-anchor:hover{color:#464646}.emoji-mart-anchor-selected .emoji-mart-anchor-bar{bottom:0}.emoji-mart-anchor-bar{position:absolute;bottom:-3px;left:0;width:100%;height:3px;background-color:#464646}.emoji-mart-anchors i{display:inline-block;width:100%;max-width:22px}.emoji-mart-anchors img,.emoji-mart-anchors svg{fill:currentColor;height:18px;width:18px}.emoji-mart-scroll{overflow-y:scroll;height:270px;padding:0 6px 6px;will-change:transform}.emoji-mart-search{margin-top:6px;padding:0 6px;position:relative}.emoji-mart-search input{font-size:16px;display:block;width:100%;padding:5px 25px 6px 10px;border-radius:5px;border:1px solid #d9d9d9;outline:0}.emoji-mart-search input,.emoji-mart-search input::-webkit-search-cancel-button,.emoji-mart-search input::-webkit-search-decoration,.emoji-mart-search input::-webkit-search-results-button,.emoji-mart-search input::-webkit-search-results-decoration{-webkit-appearance:none}.emoji-mart-search-icon{position:absolute;top:7px;right:11px;z-index:2;padding:2px 5px 1px;border:none;background:none}.emoji-mart-category .emoji-mart-emoji span{z-index:1;position:relative;text-align:center;cursor:default}.emoji-mart-category .emoji-mart-emoji:hover:before{z-index:0;content:"";position:absolute;top:0;left:0;width:100%;height:100%;background-color:#f4f4f4;border-radius:100%}.emoji-mart-category-label{z-index:2;position:relative;position:sticky;top:0}.emoji-mart-category-label span{display:block;width:100%;font-weight:500;padding:5px 6px;background-color:#fff;background-color:hsla(0,0%,100%,.95)}.emoji-mart-category-list{margin:0;padding:0}.emoji-mart-category-list li{list-style:none;margin:0;padding:0;display:inline-block}.emoji-mart-emoji{position:relative;display:inline-block;font-size:0;margin:0;padding:0;border:none;background:none;box-shadow:none}.emoji-mart-emoji-native{font-family:"Segoe UI Emoji","Segoe UI Symbol","Segoe UI","Apple Color Emoji","Twemoji Mozilla","Noto Color Emoji","EmojiOne Color","Android Emoji"}.emoji-mart-no-results{font-size:14px;text-align:center;padding-top:70px;color:#858585}.emoji-mart-no-results-img{display:block;margin-left:auto;margin-right:auto;width:50%}.emoji-mart-no-results .emoji-mart-category-label{display:none}.emoji-mart-no-results .emoji-mart-no-results-label{margin-top:.2em}.emoji-mart-no-results .emoji-mart-emoji:hover:before{content:none}.emoji-mart-preview{position:relative;height:70px}.emoji-mart-preview-data,.emoji-mart-preview-emoji,.emoji-mart-preview-skins{position:absolute;top:50%;transform:translateY(-50%)}.emoji-mart-preview-emoji{left:12px}.emoji-mart-preview-data{left:68px;right:12px;word-break:break-all}.emoji-mart-preview-skins{right:30px;text-align:right}.emoji-mart-preview-skins.custom{right:10px;text-align:right}.emoji-mart-preview-name{font-size:14px}.emoji-mart-preview-shortname{font-size:12px;color:#888}.emoji-mart-preview-emoticon+.emoji-mart-preview-emoticon,.emoji-mart-preview-shortname+.emoji-mart-preview-emoticon,.emoji-mart-preview-shortname+.emoji-mart-preview-shortname{margin-left:.5em}.emoji-mart-preview-emoticon{font-size:11px;color:#bbb}.emoji-mart-title span{display:inline-block;vertical-align:middle}.emoji-mart-title .emoji-mart-emoji{padding:0}.emoji-mart-title-label{color:#999a9c;font-size:26px;font-weight:300}.emoji-mart-skin-swatches{font-size:0;padding:2px 0;border:1px solid #d9d9d9;border-radius:12px;background-color:#fff}.emoji-mart-skin-swatches.custom{font-size:0;border:none;background-color:#fff}.emoji-mart-skin-swatches.opened .emoji-mart-skin-swatch{width:16px;padding:0 2px}.emoji-mart-skin-swatches.opened .emoji-mart-skin-swatch.selected:after{opacity:.75}.emoji-mart-skin-swatch{display:inline-block;width:0;vertical-align:middle;transition-property:width,padding;transition-duration:.125s;transition-timing-function:ease-out}.emoji-mart-skin-swatch:first-child{transition-delay:0s}.emoji-mart-skin-swatch:nth-child(2){transition-delay:.03s}.emoji-mart-skin-swatch:nth-child(3){transition-delay:.06s}.emoji-mart-skin-swatch:nth-child(4){transition-delay:.09s}.emoji-mart-skin-swatch:nth-child(5){transition-delay:.12s}.emoji-mart-skin-swatch:nth-child(6){transition-delay:.15s}.emoji-mart-skin-swatch.selected{position:relative;width:16px;padding:0 2px}.emoji-mart-skin-swatch.selected:after{content:"";position:absolute;top:50%;left:50%;width:4px;height:4px;margin:-2px 0 0 -2px;background-color:#fff;border-radius:100%;pointer-events:none;opacity:0;transition:opacity .2s ease-out}.emoji-mart-skin-swatch.custom{display:inline-block;width:0;height:38px;overflow:hidden;vertical-align:middle;transition-property:width,height;transition-duration:.125s;transition-timing-function:ease-out;cursor:default}.emoji-mart-skin-swatch.custom.selected{position:relative;width:36px;height:38px;padding:0 2px 0 0}.emoji-mart-skin-swatch.custom.selected:after{content:"";width:0;height:0}.emoji-mart-skin-swatches.custom .emoji-mart-skin-swatch.custom:hover{background-color:#f4f4f4;border-radius:10%}.emoji-mart-skin-swatches.custom.opened .emoji-mart-skin-swatch.custom{width:36px;height:38px;padding:0 2px 0 0}.emoji-mart-skin-swatches.custom.opened .emoji-mart-skin-swatch.custom.selected:after{opacity:.75}.emoji-mart-skin-text.opened{display:inline-block;vertical-align:middle;text-align:left;color:#888;font-size:11px;padding:5px 2px;width:95px;height:40px;border-radius:10%;background-color:#fff}.emoji-mart-skin{display:inline-block;width:100%;padding-top:100%;max-width:12px;border-radius:100%}.emoji-mart-skin-tone-1{background-color:#ffc93a}.emoji-mart-skin-tone-2{background-color:#fadcbc}.emoji-mart-skin-tone-3{background-color:#e0bb95}.emoji-mart-skin-tone-4{background-color:#bf8f68}.emoji-mart-skin-tone-5{background-color:#9b643d}.emoji-mart-skin-tone-6{background-color:#594539}.emoji-mart-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.emoji-mart-dark{color:#fff;background-color:#222}.emoji-mart-dark,.emoji-mart-dark .emoji-mart-bar{border-color:#555453}.emoji-mart-dark .emoji-mart-search input{color:#fff;border-color:#555453;background-color:#2f2f2f}.emoji-mart-dark .emoji-mart-search-icon svg{fill:#fff}.emoji-mart-dark .emoji-mart-category .emoji-mart-emoji:hover:before{background-color:#444}.emoji-mart-dark .emoji-mart-category-label span{background-color:#222;color:#fff}.emoji-mart-dark .emoji-mart-skin-swatches{border-color:#555453;background-color:#222}.emoji-mart-dark .emoji-mart-anchor-selected,.emoji-mart-dark .emoji-mart-anchor:focus,.emoji-mart-dark .emoji-mart-anchor:hover{color:#bfbfbf} -------------------------------------------------------------------------------- /static/js/runtime-main.5e6501f7.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,i,l=r[0],p=r[1],f=r[2],c=0,s=[];c 2 | 6 | 7 | 8 | 9 | Created by FontForge 20190112 at Tue Jun 4 15:16:44 2019 10 | By Robert Madole 11 | Copyright (c) Font Awesome 12 | 13 | 14 | 15 | 28 | 29 | 33 | 37 | 41 | 45 | 50 | 55 | 57 | 61 | 66 | 70 | 74 | 79 | 84 | 91 | 97 | 101 | 104 | 107 | 113 | 120 | 123 | 129 | 133 | 137 | 144 | 152 | 159 | 166 | 171 | 175 | 177 | 181 | 188 | 193 | 200 | 204 | 206 | 210 | 215 | 219 | 228 | 231 | 234 | 237 | 241 | 248 | 252 | 255 | 258 | 261 | 264 | 268 | 275 | 282 | 289 | 293 | 296 | 299 | 305 | 311 | 318 | 323 | 327 | 331 | 336 | 341 | 345 | 352 | 356 | 360 | 365 | 371 | 377 | 382 | 388 | 394 | 400 | 403 | 406 | 410 | 417 | 424 | 432 | 437 | 446 | 454 | 461 | 466 | 470 | 473 | 478 | 483 | 488 | 491 | 494 | 497 | 506 | 514 | 519 | 524 | 529 | 533 | 538 | 540 | 542 | 545 | 557 | 562 | 567 | 570 | 573 | 576 | 579 | 582 | 586 | 591 | 596 | 601 | 606 | 612 | 619 | 624 | 628 | 633 | 637 | 643 | 649 | 657 | 663 | 669 | 680 | 686 | 696 | 702 | 710 | 718 | 723 | 729 | 737 | 746 | 750 | 756 | 761 | 766 | 769 | 775 | 781 | 786 | 793 | 796 | 802 | 803 | 804 | -------------------------------------------------------------------------------- /static/media/outline-icons.6810be1d.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/static/media/outline-icons.6810be1d.eot -------------------------------------------------------------------------------- /static/media/outline-icons.8a7914c9.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/static/media/outline-icons.8a7914c9.woff -------------------------------------------------------------------------------- /static/media/outline-icons.a3b4cd30.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/static/media/outline-icons.a3b4cd30.ttf -------------------------------------------------------------------------------- /static/media/outline-icons.a3f7358b.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/static/media/outline-icons.a3f7358b.woff2 -------------------------------------------------------------------------------- /tips/coins1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/tips/coins1.mp3 -------------------------------------------------------------------------------- /tips/coins2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/tips/coins2.mp3 -------------------------------------------------------------------------------- /tips/gift1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/tips/gift1.png -------------------------------------------------------------------------------- /tips/gift2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/tips/gift2.png -------------------------------------------------------------------------------- /tips/gift3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/tips/gift3.png -------------------------------------------------------------------------------- /tips/gift4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/tips/gift4.png -------------------------------------------------------------------------------- /tips/gift5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/tips/gift5.png -------------------------------------------------------------------------------- /tips/register.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/tips/register.mp3 -------------------------------------------------------------------------------- /uploads/integration.txt: -------------------------------------------------------------------------------- 1 | This demonstrative edition will upload data to this folder. When integrating in own project, data should be saved in database depending on project framework. -------------------------------------------------------------------------------- /videos/hamsterad.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/videowhisper/HTML5-Videochat-PHP/52a1732b415a53f38e028d045bcb578a48152ed3/videos/hamsterad.mp4 --------------------------------------------------------------------------------