Basic problem: multiple stream subscriptions but I only want one

Hi. I’m developing an app to manage remote ip cameras. The project is based on spring and java 17, with OpenVidu 2.27 virtualized. The cameras will be grouped in buses, each one of them equipping 2 or more cameras. My app must show a list of the cameras for each bus and activate live streaming of a determined camera by clicking a button. For a reason I can’t understand, when I click for a live stream of one camera, multiple, but invisible, video elements are created. These video elements contains petitions or streaming from past tests:


<video autoplay="true" id="remote-video-str_IPC_Jn6L_ipc_IPCAM_rtsp_DWS2_192_168_1_155_554_dac_realplay_0798C961CF3FEF49A83D932662E0AF5D1_MAIN_TCP" muted="true"></video>
<video autoplay="true" id="remote-video-str_IPC_EKEQ_ipc_IPCAM_rtsp_SVAG_192_168_1_155_554_dac_realplay_59CAC59DF16C904FA12D3571FFA0F8951_MAIN_TCP" muted="true"></video>
<video autoplay="true" id="remote-video-str_IPC_VvJw_ipc_IPCAM_rtsp_FAXA_192_168_1_155_554_dac_playback_camera_59CAC59DF16C904FA12D3571FFA0F8951_MAIN_TCP" muted="true"></video>
<video autoplay="true" id="remote-video-str_IPC_NKyh_ipc_IPCAM_rtsp_KQSV_192_168_1_155_554_dac_playback_camera_59CAC59DF16C904FA12D3571FFA0F8951_MAIN_TCP" muted="true" controls=""></video></div>

How can I avoid this?. The only video playing is the one I wanted (second video element). The two last video elements contains the reference for play pre-recorded videos from the camera over rtsp. My idea is to substitute the live stream with the pre-recorded play on demand by clicking a button, but I’m not sure on how to do it. I’ve readed the tutorials, documentation and forums, but I’m totally new with camera related software and it’s obvious to me that I’m not getting it right.

This seems a problem with the management of the video players in your frontend. OpenVidu can automatically manage your video players: Manage video players - OpenVidu Docs

This means that when the IP camera stream is destroyed, the video player should be automatically removed from your web. How is your app managing the life cycle of your Subcriber streams?

First of all, thanks for answering so fast!. I tried to mimic the ipcameras code examples to a large degree. The way I’ve done is like this: First in frontend I call an ajax function with the identificators for the camera:

function getStream(camId,camName){
		cameraId = camId;
		$.ajax({
			type: 'GET',
			url: '/index/getStream',
			data: { camId: cameraId, camName: camName },
			success: function(response) {
				console.log("getStream.response: "+response);
				var jsonObject = JSON.parse(response);
				thetoken = jsonObject.token;
				subscribe(thetoken);
			},
			error: function(jqXHR) {
				alert('Error: ' + jqXHR.status);
			}
		});
	}

Then the mapped method manages the request and calls the needed methods:

@RequestMapping(value = "/index/getStream", method = RequestMethod.GET)
	@ResponseBody
	public String getStream(@RequestParam("camId") String camId,
							@RequestParam("camName") String camName,
							Model model){
            String token = subscribe(rtspUri, camId, camName);//this is delivered to the frontend
       }

The subscribe, createOpenViduSession and publishCameras methods are the same from the ipcameras github examples, except for the credentials part. Finally, the javascript “subscribe” function is:

// OpenVidu objects
	var OV;
	var session;
	var videosContainer1 = document.getElementById("video01");
	var videosContainer2 = document.getElementById("video02");
	var subscriber;

function subscribe(token) {

		// Initialize OpenVidu and Session objects
		OV = new OpenVidu();
		session = OV.initSession();

		// Connect to session. We will receive all necessary events when success
		session.connect(token)
			.catch(error => {
				var msg = 'There was an error connecting to the session. Code: ' + error.code + '. Message: ' + error
					.message;
				console.error(msg);
				alert(msg);
			});

		// On every new Stream received...
		session.on('streamCreated', event => {
			// Subscribe to the Stream to receive it
			// HTML video will be appended to a new element created inside <div id='camplayer'>
			console.log("Stream Creado");
			var videoDiv = document.createElement('div');
			var stream = event.stream;
			videoDiv.classList.add('video-container');
			videoDiv.id = stream.streamId;
			console.log("STREAM-ID: "+stream.streamId);
           
			videosContainer1.appendChild(videoDiv);
			// Append video inside our brand new <div> element
			subscriber = session.subscribe(stream, videoDiv, {insertMode: 'REPLACE'});

			// When the HTML video has been appended to DOM...
			subscriber.on('videoElementCreated', ev => {				
				// ...start loader
				var loader = document.createElement('div');
				loader.classList.add('loader');
				const videoElements = document.getElementsByTagName("video");
				for (const vs of videoElements){
					vs.setAttribute('muted', 'true');
					vs.setAttribute('autoplay', 'true');
				}
				ev.element.parentNode.insertBefore(loader, ev.element.nextSibling);
			});

			// When the HTML video starts playing...
			subscriber.on('streamPlaying', ev => {
				// ...remove loader
				var cameraVideoElement = subscriber.videos[0].video;
				//cameraVideoElement.parentNode.removeChild(cameraVideoElement.nextSibling);
				videosContainer1.removeChild(cameraVideoElement.nextSibling);
				$(".loader").remove();
				// ... mute video if browser blocked autoplay
				autoplayMutedVideoIfBlocked(cameraVideoElement);
			});

			// When the HTML video has been removed from DOM...
			subscriber.on('videoElementDestroyed', ev => {
				// ...remove the HTML elements related to the destroyed video
				var videoContainer = document.getElementById(stream.streamId);
				videoContainer.parentNode.removeChild(videoContainer);
				console.log("videoElementDestroyed");				
			});
		});

		// On every asynchronous exception...
		session.on('exception', (exception) => {
			console.warn(exception);
		});
	}

For changing the live video to a pre-recorded video first I call another ajax javascript function:

function playFile(times){
		$( "#playFile" ).prop( "disabled", true );
		$( "#getFile" ).prop( "disabled", true );		
		$.ajax({
			type: 'GET',
			url: '/index/playFileOnDemand',
			data: { cameraReference: times },
			success: function(token) {
				subscribe(token);
			},
			error: function(jqXHR) {
				alert('Error: ' + jqXHR.status);
			}
		});
	};

And the method in the backend is:

public String playFileOnDemand(String cameraReference) throws OpenViduJavaClientException, OpenViduHttpException {
		if(OV == null || OVSession == null){
			return null;
		}
		String[] values = cameraReference.split("##");
		String camId = values[0];
		String startTime = values[1];
		String rtsp01 = startTime.split("//")[0];
		String rtsp02 = startTime.split("//")[1];
		String finalRtsp = rtsp01 + "//admin:'Isa2107!'@" + rtsp02;
		
		try {
			ConnectionProperties connectionProperties = new ConnectionProperties.Builder()
					.type(ConnectionType.IPCAM)
					.data(camId)
					.rtspUri(finalRtsp)
					.adaptativeBitrate(true)
					.onlyPlayWithSubscribers(true)
					.record(false)
					.build();
			return OVSession.createConnection(connectionProperties).getToken();
		} catch (MalformedURLException e) {
			throw new RuntimeException(e);
		}
	}

As a side note, I feed OpenVidu with rtsp uris, OpenVidu is not connecting directly to the cameras. I have a rest server that provides all the rtsp uris needed, both the live rtsp and the pre-recorded video rtsp.