1
0
Fork 0
mirror of https://github.com/DanielnetoDotCom/YouPHPTube synced 2025-10-05 19:42:38 +02:00
This commit is contained in:
Daniel Neto 2025-01-11 22:06:50 -03:00
parent 5d53819459
commit 01b32b7efd
14 changed files with 347 additions and 153 deletions

1
.gitignore vendored
View file

@ -109,3 +109,4 @@ CreatePlugin/plugins/
vendor/james-heinrich/getid3/demos/
AVideoStorage/
plugin/VideoHLSOverlay/
plugin/Live/WebRTC/WebRTC2RTMP.json

Binary file not shown.

View file

@ -61,6 +61,7 @@ function sendStreamToServer(stream) {
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
//console.log(`video-chunk`);
socket.emit('video-chunk', { rtmpURLEncrypted, chunk: event.data });
}
};
@ -87,6 +88,18 @@ function stopStreamToServer() {
}
function setIsWebcamServerConnected() {
console.log('Connection success');
// Custom logic to handle connection failure
$('body').removeClass('WebcamServerNotConnected').addClass('WebcamServerConnected');
}
function setIsWebcamServerNotConnected() {
console.log('Connection error');
$('body').removeClass('WebcamServerConnected').addClass('WebcamServerNotConnected');
setIsNotLive();
}
function setIsLive() {
document.body.classList.remove('isNotLive');
document.body.classList.add('isLive');
@ -199,6 +212,16 @@ async function startWebRTC({ videoDeviceId = null, audioDeviceId = null, useScre
}
}
function toggleMediaSelector() {
if (!$('#mediaSelector').is(':visible')) {
$('#mediaSelector').fadeIn(); // Fade in #mediaSelector if not visible
$('#webrtcChat').hide(); // Hide #webrtcChat
} else {
$('#webrtcChat').show(); // Show #webrtcChat
$('#mediaSelector').fadeOut(); // Fade out #mediaSelector
}
}
window.addEventListener('orientationchange', () => {
console.log('Orientation changed.');
if (isLive) {

View file

@ -8,86 +8,93 @@ let isLive = false; // Track live status
// Handle connection errors
socket.on('connect_error', (error) => {
$('body').removeClass('WebRTCReady');
setIsWebcamServerNotConnected();
console.error('Connection error:', error.message);
// Custom logic to handle connection failure
//avideoToastError('Unable to connect to the webcam server. Please check your connection and try again.');
});
// Handle connection errors
// Handle successful connection
socket.on('connect', () => {
avideoToastSuccess('Webcam server is ready');
console.log('Connection success');
// Custom logic to handle connection failure
$('body').addClass('WebRTCReady');
setIsWebcamServerConnected();
avideoToastSuccess('Successfully connected to the webcam server.');
});
// Handle disconnection
socket.on('disconnect', (reason) => {
avideoToastError('Webcam server disconnected');
setIsWebcamServerNotConnected();
avideoToastError(`Disconnected from the webcam server. Reason: ${reason}`);
console.log('Disconnected from the server:', reason);
$('body').removeClass('WebRTCReady');
if (reason === 'io server disconnect') {
// The server disconnected the client manually
socket.connect(); // Optionally reconnect
avideoToastWarning('Reconnecting to the server...');
}
setIsNotLive();
});
// Handle reconnection attempts if enabled
// Handle reconnection attempts
socket.on('reconnect_attempt', () => {
console.log('Attempting to reconnect...');
avideoToastInfo('Attempting to reconnect to the webcam server...');
});
// Handle response
// Handle live-start
socket.on('live-start', ({ rtmpURL }) => {
console.log('live-start', rtmpURL);
avideoToastSuccess('Live start');
avideoToastSuccess(`Live streaming started successfully.`);
setIsLive();
requestNotifications();
});
// Handle response
// Handle live-resumed
socket.on('live-resumed', ({ rtmpURL }) => {
console.log('live-resumed', rtmpURL);
avideoToastSuccess('Live Resumed');
avideoToastSuccess(`Live streaming resumed.`);
setIsLive();
requestNotifications();
});
// Handle live-stopped
socket.on('live-stopped', ({ rtmpURL, message }) => {
console.log('live-stopped', rtmpURL, message);
avideoToastWarning('Live stop');
avideoToastWarning(`Live streaming stopped. Reason: ${message}`);
setIsNotLive();
requestNotifications();
});
// Handle general errors
socket.on('error', ({ message }) => {
console.error(`Error: ${message}`);
avideoToastError(message);
avideoToastError(`An error occurred: ${message}`);
requestNotifications();
});
// Handle FFMPEG errors
socket.on('ffmpeg-error', ({ code }) => {
console.error(`FFMPEG Error: ${code}`);
//avideoToastError(message);
avideoToastError(`FFMPEG encountered an error. Error code: ${code}`);
requestNotifications();
});
// Handle active connections
socket.on('connections', ({ current, max }) => {
console.log(`Current number of active connections: ${current}/${max}`);
//avideoToastInfo(`Active connections: ${current}/${max}`);
});
// Handle live-time
socket.on('live-time', ({ startTime, elapsedSeconds, remainingSeconds }) => {
console.log(`Time remaining is: ${remainingSeconds} seconds`);
//avideoToastInfo(`Live stream time remaining: ${remainingSeconds} seconds.`);
});
// Handle RTMP status
socket.on('rtmp-status', ({ rtmpURL, isRunning }) => {
if (isRunning) {
console.log(`This live is running with RTMP URL: ${rtmpURL}`);
//avideoToastSuccess(`Live stream is running. RTMP URL: ${rtmpURL}`);
setIsLive();
} else {
console.log(`This live is not running`);
//avideoToastWarning('Live stream is not running.');
setIsNotLive();
}
@ -95,7 +102,9 @@ socket.on('rtmp-status', ({ rtmpURL, isRunning }) => {
clearTimeout(liveStatusTimeout);
});
// Handle stream-stopped
socket.on('stream-stopped', ({ rtmpURL, reason }) => {
console.log(`Stream for ${rtmpURL} stopped: ${reason}`);
avideoToastWarning(`Stream stopped. Reason: ${reason}`);
requestNotifications();
});

View file

@ -55,3 +55,28 @@ function decrypt_data($ciphertextB64, $salt)
return $plaintext;
}
function getWebRTCInfo(){
global $global;
$file = "{$global['systemRootPath']}plugin/Live/WebRTC/WebRTC2RTMP.json";
if(!file_exists($file)){
return false;
}
$content = file_get_contents("{$global['systemRootPath']}plugin/Live/WebRTC/WebRTC2RTMP.json");
if(empty($content)){
return false;
}
$json = json_decode($content);
if(empty($json)){
return false;
}
return $json;
}
function getWebRTC2RTMPURL(){
$json = getWebRTCInfo();
if(empty($json)){
return '';
}
return "https://{$json->domain}:{$json->serverPort}";
}

View file

@ -19,12 +19,20 @@ if (empty($_REQUEST['avideoIframe'])) {
<?php
?>
</div>
<div id="webcamMediaControls" class="showWhenWebRTCIsReady">
<div id="webcamMediaControls" class="showWhenWebRTCIsConnected">
<?php
include __DIR__ . '/panel.medias.php';
include __DIR__ . '/panel.buttons.php';
?>
</div>
<div id="webcamMediaControlsMessage" class="alert alert-danger showWhenWebRTCIsNotConnected text-center">
<div class="fa-3x">
<i class="fa-solid fa-triangle-exclamation fa-fade"></i>
</div>
<strong>Error:</strong> Unable to connect to the Webcam server.<br>
<span>Please verify the server status and resolve any issues.</span>
</div>
<script>
$(document).ready(function() {
startWebRTC();

View file

@ -2,7 +2,7 @@
<button type="button" id="startLive" class="btn btn-success oval-menu animate__animated animate__bounceIn" onclick="startWebcamLive(rtmpURLEncrypted);">
<i class="fa fa-play"></i> Go Live
</button>
<button type="button" class="btn btn-default oval-menu animate__animated animate__bounceIn" onclick="$('#mediaSelector').fadeToggle()" style=" -webkit-animation-delay: .2s; animation-delay: .2s;">
<button type="button" class="btn btn-default oval-menu animate__animated animate__bounceIn" onclick="toggleMediaSelector();" style=" -webkit-animation-delay: .2s; animation-delay: .2s;">
<i class="fa-solid fa-ellipsis-vertical"></i>
</button>
</div>

View file

@ -7,7 +7,7 @@
include __DIR__ . '/video.php';
?>
</div>
<div class="panel-footer showWhenWebRTCIsReady">
<div class="panel-footer showWhenWebRTCIsConnected">
<?php
include __DIR__ . '/panel.medias.php';
include __DIR__ . '/panel.buttons.php';

View file

@ -1,47 +1,68 @@
body {
background-color: #000;;
background-color: #000;
;
}
/* Make the video cover the entire page */
video {
width: 100%;
height: 100%;
object-fit: contain; /* Fill the container, even if it means cropping */
object-fit: contain;
/* Fill the container, even if it means cropping */
background: black;
}
.indicator {
font-family: Arial, Helvetica, sans-serif;
position: absolute;
top: 30px;
left: 30px;
top: 40px;
left: 20px;
color: white;
font-size: 12px;
font-weight: bold;
padding: 2px 4px;
border-radius: 5px;
}
/* Live indicator */
#liveIndicator {
background: #FF0000AA;
box-shadow: 0 0 5px #FF0000; /* Initial glow */
animation: glowPulse 1.5s infinite; /* Apply the glow animation */
border: 1px solid #DDD;
border-radius: 50%;
}
#offLineIndicator {
background: #CCCCCCAA;
background: #FF000033;
color: #CCC;
border-color: #FAA;
}
/* Keyframes for glowing pulse animation */
@keyframes glowPulse {
/* Live indicator */
#onLineIndicator {
background: #00000055;
color: #DDD;
}
/* Live indicator (Green) */
#liveIndicator {
background: #00FF00AA;
box-shadow: 0 0 5px #00FF00;
/* Initial glow */
animation: greenGlowPulse 1.5s infinite;
/* Apply the glow animation */
border-color: 2px solid #55FF55;
}
/* Keyframes for green glowing pulse animation */
@keyframes greenGlowPulse {
0% {
box-shadow: 0 0 5px #FF0000; /* Starting soft glow */
box-shadow: 0 0 5px #00FF00;
/* Starting soft glow */
}
50% {
box-shadow: 0 0 15px #FF0000; /* Intense glow */
box-shadow: 0 0 15px #00FF00;
/* Intense glow */
}
100% {
box-shadow: 0 0 5px #FF0000; /* Back to soft glow */
box-shadow: 0 0 5px #00FF00;
/* Back to soft glow */
}
}
@ -61,25 +82,31 @@ video {
display: none !important;
}
.showWhenWebRTCIsReady{
.showWhenWebRTCIsConnected,
.showWhenWebRTCIsNotConnected {
display: none !important;
}
.WebRTCReady .showWhenWebRTCIsReady{
.WebcamServerConnected .showWhenWebRTCIsConnected {
display: block !important;
}
#webcamMediaControls{
.WebcamServerNotConnected .showWhenWebRTCIsNotConnected {
display: block !important;
}
#webcamMediaControls, #webcamMediaControlsMessage {
position: fixed;
bottom: 10px;
width: 100%;
}
.oval-menu {
height: 50px;
min-width: 50px;
border-radius: 25px;
height: 30px;
min-width: 30px;
border-radius: 15px;
box-shadow: 0 0 15px 1px black;
font-size: 15px;
font-size: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
@ -89,3 +116,20 @@ video {
.oval-menu i {
margin: 0 10px;
}
.transparent-iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: calc(100% - 50px);
/* Occupies full height minus 100px at the bottom */
border: none;
/* Removes the border */
background: transparent;
/* Ensures transparency if supported by the content */
z-index: 1;
/* Ensure proper stacking order */
pointer-events: auto;
/* Allows interaction with iframe content */
}

View file

@ -1,22 +1,27 @@
<?php
require_once __DIR__ . '/functions.php';
// Use parse_url to extract components of the URL
$parsedUrl = parse_url($global['webSiteRootURL']);
// Get the domain (host) from the parsed URL
$domain = $parsedUrl['host'] ?? null;
global $global;
$global['doNotLoadPlayer'] = 1;
$forceIndex = 'Webcam';
$rtmpURL = Live::getRTMPLink(User::getId(), $forceIndex);
$key = Live::getKeyFromUser(User::getId());
?>
<script class="doNotSepareteTag">
// Send streamKey to the server when joining
var rtmpURLEncrypted = '<?php echo encrypt_data(Live::getRTMPLink(User::getId(), 'Webcam'), $global['saltV2']); ?>';
var WebRTC2RTMPURL = 'https://<?php echo $domain; ?>:3000';
var rtmpURLEncrypted = '<?php echo encrypt_data($rtmpURL, $global['saltV2']); ?>';
var WebRTC2RTMPURL = '<?php echo getWebRTC2RTMPURL(); ?>';
</script>
<link href="<?php echo getURL('plugin/Live/WebRTC/style.css'); ?>" rel="stylesheet" type="text/css" />
<div id="liveIndicator" class="showWhenIsLive indicator" style="display: none;">LIVE</div>
<div id="offLineIndicator" class="showWhenIsNotLive indicator" style="display: none;">OFFLINE</div>
<div id="offLineIndicator" class="showWhenWebRTCIsNotConnected indicator" style="display: none;">
<div>
<i class="fa-solid fa-wifi"></i>
<i class="fa-solid fa-slash" style="position: absolute;left: 4px;top: 3px;"></i>
</div>
</div>
<div id="onLineIndicator" class="showWhenWebRTCIsConnected showWhenIsNotLive indicator" style="display: none;"><i class="fa-solid fa-wifi"></i></div>
<div id="liveIndicator" class="showWhenIsLive indicator" style="display: none;"><i class="fa-solid fa-wifi"></i></div>
<video id="localVideo" autoplay muted playsinline></video>
<iframe id="webrtcChat" class="transparent-iframe" src="<?php echo $global['webSiteRootURL']; ?>plugin/MobileYPT/index.php?key=<?php echo $key; ?>&live_index=<?php echo $forceIndex; ?>"></iframe>
<script src="<?php echo getURL('node_modules/socket.io-client/dist/socket.io.min.js'); ?>" type="text/javascript"></script>
<script src="<?php echo getURL('plugin/Live/WebRTC/api.js'); ?>" type="text/javascript"></script>
<script src="<?php echo getURL('plugin/Live/WebRTC/events.js'); ?>" type="text/javascript"></script>

View file

@ -12,6 +12,9 @@ function isInLive(json) {
var prerollPosterAlreadyPlayed = false;
async function showImage(type, key) {
if (typeof player === 'undefined') {
return false;
}
if (typeof closeLiveImageRoll == 'function') {
closeLiveImageRoll();
}

View file

@ -1,17 +1,35 @@
function socketLiveONCallback(json) {
document.addEventListener('socketLiveONCallback', function(event) {
let json = event.detail;
if(json === null){
console.error('socketLiveONCallback socket EventListener error', event);
return false;
}
console.log('socketLiveONCallback socket EventListener', json);
if (typeof json === 'string') {
try {
json = JSON.parse(json);
} catch (error) {
console.error("Invalid JSON string:", error);
return null; // or return the original string if you prefer
return; // Exit the listener if JSON parsing fails
}
}
if(typeof json.key == 'undefined'){
if(typeof json.json !== 'undefined' && typeof json.json.key !== 'undefined' ){
json = json.json;
}else{
console.error("socketLiveONCallback Invalid JSON key not found:", json);
return; // Exit the listener if JSON parsing fails
}
}
console.log('socketLiveONCallback live plugin', json);
if (typeof processLiveStats == 'function') {
processLiveStats(json.stats);
}
var selector = '.live_' + json.live_servers_id + "_" + json.key;
let selector = '.live_' + json.live_servers_id + "_" + json.key;
$(selector).slideDown();
if (typeof onlineLabelOnline == 'function') {
@ -23,38 +41,58 @@ function socketLiveONCallback(json) {
onlineLabelOnline(selector);
}
// update the chat if the history changes
var IframeClass = ".yptchat2IframeClass_" + json.key + "_" + json.live_servers_id;
let IframeClass = ".yptchat2IframeClass_" + json.key + "_" + json.live_servers_id;
if ($(IframeClass).length) {
var src = $(IframeClass).attr('src');
let src = $(IframeClass).attr('src');
if (src) {
avideoToast('Loading new chat');
var newSRC = addGetParam(src, 'live_transmitions_history_id', json.live_transmitions_history_id);
let newSRC = addGetParam(src, 'live_transmitions_history_id', json.live_transmitions_history_id);
$(IframeClass).attr('src', newSRC);
}
}
if (isInLive(json)) {
playerPlay();
showImage('prerollPoster', json.cleanKey);
}
});
document.addEventListener('socketLiveOFFCallback', function(event) {
let json = event.detail;
if(json === null){
console.error('socketLiveOFFCallback socket EventListener error', event);
console.trace();
return false;
}
function socketLiveOFFCallback(json) {
console.log('socketLiveOFFCallback socket EventListener', json);
if (typeof json === 'string') {
try {
json = JSON.parse(json);
} catch (error) {
console.error("Invalid JSON string:", error);
return null; // or return the original string if you prefer
return; // Exit the listener if JSON parsing fails
}
}
if(typeof json.key == 'undefined'){
if(typeof json.json !== 'undefined' && typeof json.json.key !== 'undefined' ){
json = json.json;
}else{
console.error("socketLiveOFFCallback Invalid JSON key not found:", json);
return; // Exit the listener if JSON parsing fails
}
}
console.log('socketLiveOFFCallback live socket', json);
var selector = '.live_' + json.live_servers_id + "_" + json.key;
let selector = '.live_' + json.live_servers_id + "_" + json.key;
selector += ', .liveVideo_live_' + json.live_servers_id + "_" + json.key;
selector += ', .live_' + json.key;
////console.log('socketLiveOFFCallback 1', selector);
$(selector).slideUp("fast", function () {
$(this).remove();
});
if (typeof onlineLabelOffline == 'function') {
selector = '#liveViewStatusID_' + json.key + '_' + json.live_servers_id;
selector += ', .liveViewStatusClass_' + json.key + '_' + json.live_servers_id;
@ -63,8 +101,8 @@ function socketLiveOFFCallback(json) {
console.log('socketLiveOFFCallback', selector);
onlineLabelOffline(selector);
}
setTimeout(function () {
//console.log('socketLiveOFFCallback processLiveStats');
if (typeof processLiveStats == 'function') {
processLiveStats(json.stats);
}
@ -78,10 +116,12 @@ function socketLiveOFFCallback(json) {
if (isInLive(json)) {
showImage('postrollPoster', json.cleanKey);
}
if (typeof updateUserNotificationCount == 'function') {
updateUserNotificationCount();
}
}
});
function redirectLive(json, countdown = 15) {
if (typeof json === 'string') {

View file

@ -1,6 +1,7 @@
<?php
global $global, $config;
$global['isIframe'] = 1;
$global['isNotAPlayer'] = 1;
// is online
// recorder
// live users
@ -17,7 +18,7 @@ $global['skippPlugins'][] = 'TheaterButton';
//$global['doNotLoadPlayer'] = 1;
$bodyClass = '';
$key = '';
$live_servers_id = '';
$live_servers_id = 0;
$live_index = '';
$users_id = User::getId();
if (!empty($_REQUEST['logoff'])) {
@ -28,12 +29,12 @@ User::loginFromRequestIfNotLogged();
if (User::isLogged()) {
if (!empty($_REQUEST['key'])) {
$key = $_REQUEST['key'];
$live_servers_id = @$_REQUEST['live_servers_id'];
$live_servers_id = intval(@$_REQUEST['live_servers_id']);
$live_index = @$_REQUEST['live_index'];
} else if (User::isLogged()) {
$lth = LiveTransmitionHistory::getLatestFromUser($users_id);
$key = $lth['key'];
$live_servers_id = $lth['live_servers_id'];
$live_servers_id = intval($lth['live_servers_id']);
$live_index = @$lth['live_index'];
}
@ -59,9 +60,11 @@ if (User::isLogged()) {
$chat->set_doNotAllowUsersSendMessagesToEachOther(1);
$chat->set_hideBubbleButtons(1);
$iframeURL = $chat->getURL(true);
$iframeClass = "yptchat2IframeClass_{$key}-{$live_index}_{$live_servers_id}";
$html = '<iframe
id="yptchat2Iframe"
class="' . $iframeClass . '"
src="' . $iframeURL . '"
frameborder="0" scrolling="no" title="chat widget"
allowtransparency="true"
@ -112,6 +115,7 @@ if (User::isLogged()) {
?>
<!DOCTYPE html>
<html lang="">
<head>
<style>
body {
@ -122,9 +126,12 @@ if (User::isLogged()) {
include $global['systemRootPath'] . 'view/include/head.php';
?>
<style>
#accessibility-toolbar, footer, #socket_info_container{
#accessibility-toolbar,
footer,
#socket_info_container {
display: none !important;
}
body {
padding: 0;
}
@ -133,22 +140,28 @@ if (User::isLogged()) {
position: fixed;
top: 10px !important;
}
.liveUsersLabel {
left: 20px !important;
}
#recorderToEncoderActionButtons {
position: absolute;
top: 40px;
left: 0;
width: 100%;
}
.showWhenClosed, #closeRecorderButtons{
.showWhenClosed,
#closeRecorderButtons {
display: none;
}
#recorderToEncoderActionButtons.closed .recordLiveControlsDiv,
#recorderToEncoderActionButtons.closed .hideWhenClosed {
display: none !important;
}
#recorderToEncoderActionButtons.closed .showWhenClosed,
.isLiveOnline #closeRecorderButtons {
display: inline-block !important;
@ -164,18 +177,27 @@ if (User::isLogged()) {
include $global['systemRootPath'] . 'view/include/footer.php';
?>
<script>
function socketLiveONCallback(json) {
console.log('socketLiveONCallback MobileYPT', json);
if ((json.users_id == '<?php echo User::getId(); ?>' && json.live_transmitions_history_id) || (!empty(json.key) && json.key == '<?php echo @$_REQUEST['key']; ?>')) {
document.addEventListener('socketLiveONCallback', function(event) {
const json = event.detail; // Extract the JSON data from the event's detail property
console.log('socketLiveONCallback EventListener', json);
if(json == null){
console.trace();
return false;
}
if (
(json.users_id == '<?php echo User::getId(); ?>' && json.live_transmitions_history_id) ||
(json.key && json.key == '<?php echo @$_REQUEST['key']; ?>')
) {
modal.showPleaseWait();
var url = addGetParam(window.location.href, 'live_transmitions_history_id', json.live_transmitions_history_id);
let url = addGetParam(window.location.href, 'live_transmitions_history_id', json.live_transmitions_history_id);
url = addGetParam(url, 'key', json.key);
url = addGetParam(url, 'live_servers_id', json.live_servers_id);
url = addGetParam(url, 'live_schedule', json.live_schedule);
url = addGetParam(url, 'live_index', json.live_index);
document.location = url;
}
}
});
</script>
</body>
</html>

View file

@ -41,17 +41,31 @@ function processSocketJson(json) {
}
} else {
var myfunc;
var _details = json;
if(typeof json.msg != 'undefined'){
_details = json.msg;
}
if(typeof _details === 'string'){
_details = JSON.parse(_details);
console.log('processSocketJson: details after', _details);
}
if (json.callback) {
//console.log("processSocketJson json.callback ", json.resourceId, json.callback);
// Check if a function exists with the name in json.callback
var code = "if (typeof " + json.callback + " == 'function') { myfunc = " + json.callback + "; } else { myfunc = defaultCallback; }";
console.log('processSocketJson: code=' + code);
console.log('processSocketJson: code=' + code, _details);
eval(code);
// Trigger the event with the same name as json.callback and pass the JSON object
const event = new CustomEvent(json.callback, { detail: _details }); // Pass the JSON as `detail`
document.dispatchEvent(event);
} else {
//console.log("processSocketJson: callback not found", json);
console.log("processSocketJson: callback not found", json);
myfunc = defaultCallback;
}
//console.log("onmessage: callback ", myfunc, json.msg);
myfunc(json.msg);
// Call the function and pass the JSON object
myfunc(_details);
}
}