Working NVR plugin
This commit is contained in:
parent
3a278b0ca6
commit
2501831a59
15 changed files with 268 additions and 57 deletions
BIN
plugins/hal-nvr/resource/resource/hal-nvr-reference.db
Normal file
BIN
plugins/hal-nvr/resource/resource/hal-nvr-reference.db
Normal file
Binary file not shown.
|
|
@ -82,9 +82,9 @@
|
|||
<div class="form-group">
|
||||
<label class="control-label">Type:</label>
|
||||
<select class="form-control" name="type">
|
||||
{{#availableCameras}}
|
||||
{{#availableCameraConfigClasses}}
|
||||
<option>{{.getName()}}</option>
|
||||
{{/availableCameras}}
|
||||
{{/availableCameraConfigClasses}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
|
@ -102,7 +102,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="camera-data-conf-template" class="hidden">
|
||||
{{#cameraConf}}
|
||||
{{#availableCameraObjectConfig}}
|
||||
<div id="{{.clazz.getName()}}">
|
||||
{{#.params}}
|
||||
<div class="form-group">
|
||||
|
|
@ -118,6 +118,6 @@
|
|||
</div>
|
||||
{{/.params}}
|
||||
</div>
|
||||
{{/cameraConf}}
|
||||
{{/availableCameraObjectConfig}}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
<h1 class="page-header">Details for <a href="#">{{camera.getName()}}</a></h1>
|
||||
|
||||
<style>
|
||||
#view-main {
|
||||
width: 100%;
|
||||
height: 480px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="col-md-12">
|
||||
<video width="100%" height="480" controls>
|
||||
<source src="rtsp://admin:xxxx@192.168.10.223:554/H.264">
|
||||
Your browser does not support the video tag.
|
||||
<video id="view-main" width="100%" height="480" class="video-js vjs-fill" preload="auto" data-setup='{}' controls autoplay muted >
|
||||
<source src="{{camera.getPlaylistRelativeUrl()}}">
|
||||
<p class="vjs-no-js">
|
||||
To view this video please enable JavaScript, and consider upgrading to a web browser that
|
||||
<a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video.</a>
|
||||
</p>
|
||||
</video>
|
||||
</div>
|
||||
|
||||
|
|
@ -24,7 +34,7 @@
|
|||
</thead>
|
||||
<tr>
|
||||
<th class="text-right">Type:</th>
|
||||
<td>{{camera.getDeviceData().getClass().getSimpleName()}}</td>
|
||||
<td>{{camera.getDeviceConfig().getClass().getSimpleName()}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-right">Owner:</th>
|
||||
|
|
@ -40,3 +50,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<link href="css/video-js.min.css" rel="stylesheet">
|
||||
<script src="js/video.min.js"></script>
|
||||
<script src="js/videojs-http-streaming.min.js"></script>
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
<h1 class="page-header">Camera Monitor</h1>
|
||||
|
||||
<style>
|
||||
.monitor-view {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
height: 740px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -11,7 +9,7 @@
|
|||
<tr>
|
||||
<td>
|
||||
<video id="view1" class="video-js vjs-fill" preload="auto" data-setup='{}' controls autoplay muted >
|
||||
<source src="{{stream1}}">
|
||||
<source src="{{camera1.getPlaylistRelativeUrl()}}">
|
||||
<p class="vjs-no-js">
|
||||
To view this video please enable JavaScript, and consider upgrading to a web browser that
|
||||
<a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video.</a>
|
||||
|
|
@ -21,7 +19,7 @@
|
|||
|
||||
<td>
|
||||
<video id="view2" class="video-js vjs-fill" preload="auto" data-setup='{}' controls autoplay muted >
|
||||
<source src="{{stream2}}">
|
||||
<source src="{{camera2.getPlaylistRelativeUrl()}}">
|
||||
</video>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -29,13 +27,13 @@
|
|||
<tr>
|
||||
<td>
|
||||
<video id="view3" class="video-js vjs-fill" preload="auto" data-setup='{}' controls autoplay muted >
|
||||
<source src="{{stream3}}">
|
||||
<source src="{{camera3.getPlaylistRelativeUrl()}}">
|
||||
</video>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<video id="view4" class="video-js vjs-fill" preload="auto" data-setup='{}' controls autoplay muted >
|
||||
<source src="{{stream4}}">
|
||||
<source src="{{camera4.getPlaylistRelativeUrl()}}">
|
||||
</video>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -9,15 +9,11 @@
|
|||
<thead>
|
||||
<th class="col-md-4">Name</th>
|
||||
<th class="col-md-3">Type</th>
|
||||
<th class="col-md-2">Data</th>
|
||||
<th class="col-md-2">Last Update</th>
|
||||
</thead>
|
||||
{{#cameras}}
|
||||
<tr>
|
||||
<td><a href="?id={{.getId()}}">{{.getName()}}</a></td>
|
||||
<td>{{.getDeviceConfig().getClass().getSimpleName()}}</td>
|
||||
<td>{{.getDeviceData()}}</td>
|
||||
<td><span class="timestamp">{{.getDeviceData().getTimestamp()}}</span></td>
|
||||
</tr>
|
||||
{{/cameras}}
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,22 @@
|
|||
package se.hal.plugin.nvr;
|
||||
|
||||
import se.hal.HalContext;
|
||||
import se.hal.intf.HalAbstractControllerManager;
|
||||
import se.hal.intf.HalEventController;
|
||||
import se.hal.plugin.nvr.intf.HalCameraConfig;
|
||||
import se.hal.plugin.nvr.intf.HalCameraController;
|
||||
import se.hal.plugin.nvr.struct.Camera;
|
||||
import se.hal.struct.Sensor;
|
||||
import se.hal.util.HalDeviceUtil;
|
||||
import zutil.db.DBConnection;
|
||||
import zutil.log.LogUtil;
|
||||
import zutil.plugin.PluginManager;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
|
|
@ -21,14 +27,66 @@ public class CameraControllerManager extends HalAbstractControllerManager<HalCam
|
|||
/** List of all registered cameras **/
|
||||
private List<Camera> registeredCameras = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
@Override
|
||||
public void register(Camera device) {
|
||||
|
||||
public void initialize(PluginManager pluginManager){
|
||||
super.initialize(pluginManager);
|
||||
instance = this;
|
||||
|
||||
// Read in existing devices
|
||||
|
||||
try {
|
||||
DBConnection db = HalContext.getDB();
|
||||
|
||||
logger.info("Reading in existing cameras.");
|
||||
|
||||
for (Camera camera : Camera.getCameras(db)) {
|
||||
register(camera);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.log(Level.SEVERE, "Unable to read in existing cameras.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void register(Camera camera) {
|
||||
if (camera.getDeviceConfig() == null) {
|
||||
logger.warning("Camera config is null: " + camera);
|
||||
return;
|
||||
}
|
||||
if (!getAvailableDeviceConfigs().contains(camera.getDeviceConfig().getClass())) {
|
||||
logger.warning("Camera data plugin not available: " + camera.getDeviceConfig().getClass());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Registering new camera(id: " + camera.getId() + "): " + camera.getDeviceConfig().getClass());
|
||||
Class<? extends HalCameraController> controllerClass = (Class<? extends HalCameraController>) camera.getControllerClass();
|
||||
HalCameraController controller = getControllerInstance(controllerClass);
|
||||
|
||||
if (controller != null)
|
||||
controller.register(camera.getDeviceConfig());
|
||||
|
||||
registeredCameras.add(camera);
|
||||
//detectedCameras.remove(HalDeviceUtil.findDevice(camera.getDeviceConfig(), detectedCameras)); // Remove if this device was detected
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deregister(Camera device) {
|
||||
public void deregister(Camera camera) {
|
||||
if (camera.getDeviceConfig() == null) {
|
||||
logger.warning("Camera config is null: " + camera);
|
||||
return;
|
||||
}
|
||||
|
||||
Class<? extends HalCameraController> controllerClass = (Class<? extends HalCameraController>) camera.getControllerClass();
|
||||
HalCameraController controller = (HalCameraController) controllerMap.get(controllerClass);
|
||||
if (controller != null) {
|
||||
logger.info("Deregistering camera(id: " + camera.getId() + "): " + camera.getDeviceConfig().getClass());
|
||||
controller.deregister(camera.getDeviceConfig());
|
||||
registeredCameras.remove(camera);
|
||||
removeControllerIfEmpty(controller);
|
||||
} else {
|
||||
logger.warning("Controller not instantiated: " + camera.getControllerClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -47,13 +105,6 @@ public class CameraControllerManager extends HalAbstractControllerManager<HalCam
|
|||
|
||||
}
|
||||
|
||||
|
||||
public void initialize(PluginManager pluginManager){
|
||||
super.initialize(pluginManager);
|
||||
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static CameraControllerManager getInstance(){
|
||||
return instance;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package se.hal.plugin.nvr;
|
||||
|
||||
import se.hal.HalContext;
|
||||
import se.hal.intf.HalDatabaseUpgrader;
|
||||
|
||||
/**
|
||||
* The DB upgrade class for Hal-NVR plugin
|
||||
*/
|
||||
public class NVRDatabaseUpgrader extends HalDatabaseUpgrader {
|
||||
private static final int REFERENCE_DB_VERSION = 1;
|
||||
private static final String REFERENCE_DB_PATH = HalContext.RESOURCE_ROOT + "/resource/hal-nvr-reference.db";
|
||||
|
||||
|
||||
public NVRDatabaseUpgrader() {
|
||||
super(REFERENCE_DB_VERSION, REFERENCE_DB_PATH);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
package se.hal.plugin.nvr.page;
|
||||
|
||||
import se.hal.EventControllerManager;
|
||||
import se.hal.HalContext;
|
||||
import se.hal.intf.HalWebPage;
|
||||
import se.hal.page.HalAlertManager;
|
||||
|
|
@ -83,7 +84,7 @@ public class CameraConfigWebPage extends HalWebPage {
|
|||
camera.setUser(localUser);
|
||||
camera.getDeviceConfigurator().setValues(request).applyConfiguration();
|
||||
camera.save(db);
|
||||
//ControllerManager.getInstance().register(camera);
|
||||
CameraControllerManager.getInstance().register(camera);
|
||||
|
||||
HalAlertManager.getInstance().addAlert(new UserMessage(
|
||||
MessageLevel.SUCCESS, "Successfully created new camera: " + camera.getName(), MessageTTL.ONE_VIEW));
|
||||
|
|
@ -100,7 +101,7 @@ public class CameraConfigWebPage extends HalWebPage {
|
|||
camera.save(db);
|
||||
|
||||
HalAlertManager.getInstance().addAlert(new UserMessage(
|
||||
MessageLevel.SUCCESS, "Successfully saved camera: "+camera.getName(), MessageTTL.ONE_VIEW));
|
||||
MessageLevel.SUCCESS, "Successfully saved camera: " + camera.getName(), MessageTTL.ONE_VIEW));
|
||||
} else {
|
||||
logger.warning("Unknown camera id: " + id);
|
||||
HalAlertManager.getInstance().addAlert(new UserMessage(
|
||||
|
|
@ -112,15 +113,15 @@ public class CameraConfigWebPage extends HalWebPage {
|
|||
camera = Camera.getCamera(db, id);
|
||||
if (camera != null) {
|
||||
logger.info("Removing camera: " + camera.getName());
|
||||
//ControllerManager.getInstance().deregister(camera);
|
||||
CameraControllerManager.getInstance().deregister(camera);
|
||||
camera.delete(db);
|
||||
|
||||
HalAlertManager.getInstance().addAlert(new UserMessage(
|
||||
MessageLevel.SUCCESS, "Successfully removed camera: "+camera.getName(), MessageTTL.ONE_VIEW));
|
||||
MessageLevel.SUCCESS, "Successfully removed camera: " + camera.getName(), MessageTTL.ONE_VIEW));
|
||||
} else {
|
||||
logger.warning("Unknown camera id: " + id);
|
||||
HalAlertManager.getInstance().addAlert(new UserMessage(
|
||||
MessageLevel.ERROR, "Unknown camera id: "+id, MessageTTL.ONE_VIEW));
|
||||
MessageLevel.ERROR, "Unknown camera id: " + id, MessageTTL.ONE_VIEW));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -129,6 +130,8 @@ public class CameraConfigWebPage extends HalWebPage {
|
|||
// Output
|
||||
Templator tmpl = new Templator(FileUtil.find(TEMPLATE));
|
||||
tmpl.set("cameras", Camera.getCameras(db));
|
||||
tmpl.set("availableCameraConfigClasses", CameraControllerManager.getInstance().getAvailableDeviceConfigs());
|
||||
tmpl.set("availableCameraObjectConfig", cameraConfigurations);
|
||||
|
||||
return tmpl;
|
||||
|
||||
|
|
|
|||
|
|
@ -72,11 +72,11 @@ public class CameraOverviewWebPage extends HalWebPage {
|
|||
return tmpl;
|
||||
}
|
||||
else {
|
||||
List<Event> events = Event.getLocalEvents(db);
|
||||
Collections.sort(events, DeviceNameComparator.getInstance());
|
||||
List<Camera> cameras = Camera.getCameras(db);
|
||||
Collections.sort(cameras, DeviceNameComparator.getInstance());
|
||||
|
||||
Templator tmpl = new Templator(FileUtil.find(OVERVIEW_TEMPLATE));
|
||||
tmpl.set("events", events);
|
||||
tmpl.set("cameras", cameras);
|
||||
return tmpl;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,12 @@ package se.hal.plugin.nvr.page;
|
|||
|
||||
import se.hal.HalContext;
|
||||
import se.hal.intf.HalWebPage;
|
||||
import se.hal.plugin.nvr.struct.Camera;
|
||||
import zutil.db.DBConnection;
|
||||
import zutil.io.file.FileUtil;
|
||||
import zutil.parser.Templator;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MonitorWebPage extends HalWebPage {
|
||||
|
|
@ -45,10 +47,11 @@ public class MonitorWebPage extends HalWebPage {
|
|||
DBConnection db = HalContext.getDB();
|
||||
|
||||
Templator tmpl = new Templator(FileUtil.find(TEMPLATE));
|
||||
tmpl.set("stream1", "https://multiplatform-f.akamaihd.net/i/multi/will/bunny/big_buck_bunny_,640x360_400,640x360_700,640x360_1000,950x540_1500,.f4v.csmil/master.m3u8");
|
||||
tmpl.set("stream2", "http://vjs.zencdn.net/v/oceans.mp4");
|
||||
tmpl.set("stream3", "http://vjs.zencdn.net/v/oceans.mp4");
|
||||
tmpl.set("stream4", "http://vjs.zencdn.net/v/oceans.mp4");
|
||||
|
||||
List<Camera> cameras = Camera.getCameras(db);
|
||||
for (int i = 0; i < cameras.size(); i++) {
|
||||
tmpl.set("camera" + (i+1), cameras.get(i));
|
||||
}
|
||||
|
||||
return tmpl;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@
|
|||
"name": "Hal-NVR",
|
||||
"description": "A Network Video Recorder plugin for recording network streams.",
|
||||
"interfaces": [
|
||||
{"se.hal.intf.HalDatabaseUpgrader": "se.hal.plugin.nvr.NVRDatabaseUpgrader"},
|
||||
{"se.hal.intf.HalAbstractControllerManager": "se.hal.plugin.nvr.CameraControllerManager"},
|
||||
|
||||
{"se.hal.plugin.nvr.intf.HalCameraConfig": "se.hal.plugin.nvr.rtsp.RTSPCameraConfig"},
|
||||
|
||||
{"se.hal.intf.HalWebPage": "se.hal.plugin.nvr.page.CameraConfigWebPage"},
|
||||
{"se.hal.intf.HalWebPage": "se.hal.plugin.nvr.page.CameraOverviewWebPage"},
|
||||
{"se.hal.intf.HalWebPage": "se.hal.plugin.nvr.page.MonitorWebPage"}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ package se.hal.plugin.nvr.rtsp;
|
|||
import se.hal.intf.HalDeviceData;
|
||||
import se.hal.plugin.nvr.intf.HalCameraConfig;
|
||||
import se.hal.plugin.nvr.intf.HalCameraController;
|
||||
import zutil.ui.conf.Configurator;
|
||||
|
||||
public class RTSPCameraConfig implements HalCameraConfig {
|
||||
|
||||
@Configurator.Configurable(value = "RTSP URL", description = "Url to the RTSP stream of the camera. (Should start with rtsp://)")
|
||||
private String rtspUrl;
|
||||
|
||||
|
||||
|
|
@ -54,10 +56,24 @@ public class RTSPCameraConfig implements HalCameraConfig {
|
|||
return null; // TODO:
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof RTSPCameraConfig)
|
||||
return rtspUrl.equals(((RTSPCameraConfig) obj).rtspUrl);
|
||||
return false;
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof RTSPCameraConfig)) return false;
|
||||
|
||||
RTSPCameraConfig that = (RTSPCameraConfig) o;
|
||||
|
||||
return rtspUrl != null ? rtspUrl.equals(that.rtspUrl) : that.rtspUrl == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return rtspUrl != null ? rtspUrl.hashCode() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "URL: " + rtspUrl;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
package se.hal.plugin.nvr.rtsp;
|
||||
|
||||
import se.hal.HalContext;
|
||||
import zutil.io.file.FileUtil;
|
||||
import zutil.log.LogUtil;
|
||||
import zutil.osal.OSALBinaryManager;
|
||||
import zutil.osal.app.ffmpeg.FFmpeg;
|
||||
import zutil.osal.app.ffmpeg.FFmpegConstants;
|
||||
import zutil.osal.app.ffmpeg.FFmpegInput;
|
||||
import zutil.osal.app.ffmpeg.FFmpegOutput;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
|
@ -11,11 +21,21 @@ import java.util.logging.Logger;
|
|||
public class RTSPCameraRecorder implements Runnable {
|
||||
private static final Logger logger = LogUtil.getLogger();
|
||||
|
||||
private static final File FFMPEG_BINARY_PATH = FileUtil.find(HalContext.RESOURCE_ROOT + "/resource/bin/");
|
||||
|
||||
private RTSPCameraConfig camera;
|
||||
private String storagePath;
|
||||
private Process process;
|
||||
|
||||
|
||||
public RTSPCameraRecorder(RTSPCameraConfig camera) {
|
||||
public RTSPCameraRecorder(RTSPCameraConfig camera, String storagePath) {
|
||||
this.camera = camera;
|
||||
this.storagePath = storagePath;
|
||||
}
|
||||
|
||||
|
||||
public RTSPCameraConfig getCamera() {
|
||||
return camera;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -24,15 +44,81 @@ public class RTSPCameraRecorder implements Runnable {
|
|||
logger.info("Starting up RTSP Stream recording thread for: " + camera.getRtspUrl());
|
||||
|
||||
try {
|
||||
new File(storagePath).mkdirs();
|
||||
|
||||
// ----------------------------------
|
||||
// Setup commandline
|
||||
// ----------------------------------
|
||||
|
||||
FFmpegInput ffmpegInput = new FFmpegInput(camera.getRtspUrl());
|
||||
|
||||
//FFmpegOutput ffmpegOutput = new FFmpegOutput(storagePath + File.separator + "stream.mp4");
|
||||
FFmpegOutput ffmpegOutput = new FFmpegOutput(new File(storagePath, "stream_%v/stream.m3u8").getPath());
|
||||
/*ffmpegOutput.addAdditionalArg("-filter_complex \"[0:v]split=3[v1][v2][v3]; [v1]copy[v1out]; [v2]scale=w=1280:h=720[v2out]; [v3]scale=w=640:h=360[v3out]\"",
|
||||
"-map [v1out] -c:v:0 libx264 -x264-params \"nal-hrd=cbr:force-cfr=1\" -b:v:0 5M -maxrate:v:0 5M -minrate:v:0 5M -bufsize:v:0 10M -preset veryfast -g 25 -sc_threshold 0",
|
||||
"-map [v2out] -c:v:1 libx264 -x264-params \"nal-hrd=cbr:force-cfr=1\" -b:v:1 3M -maxrate:v:1 3M -minrate:v:1 3M -bufsize:v:1 3M -preset veryfast -g 25 -sc_threshold 0",
|
||||
"-map [v3out] -c:v:2 libx264 -x264-params \"nal-hrd=cbr:force-cfr=1\" -b:v:2 1M -maxrate:v:2 1M -minrate:v:2 1M -bufsize:v:2 1M -preset veryfast -g 25 -sc_threshold 0",
|
||||
"-map a:0 -c:a:0 aac -b:a:0 96k -ac 2",
|
||||
"-map a:0 -c:a:1 aac -b:a:1 96k -ac 2",
|
||||
"-map a:0 -c:a:2 aac -b:a:2 48k -ac 2",
|
||||
"-var_stream_map \"v:0,a:0,name:Source v:1,a:1,name:720p v:2,a:2,name:360p\""
|
||||
);*/
|
||||
ffmpegOutput.addAdditionalArg(
|
||||
"-c:v:0 libx264 -x264-params \"nal-hrd=cbr:force-cfr=1\" -b:v:0 5M -maxrate:v:0 5M -minrate:v:0 5M -bufsize:v:0 10M -preset veryfast -g 25 -sc_threshold 0"
|
||||
);
|
||||
ffmpegOutput.addAdditionalArg("-f hls",
|
||||
"-hls_time 2", // segment length in seconds
|
||||
//"-hls_playlist_type event", // Do not delete old segments
|
||||
"-hls_flags independent_segments+delete_segments",
|
||||
"-hls_segment_type mpegts",
|
||||
"-hls_segment_filename \"" + new File(storagePath, "stream_%v/data%02d.ts").getPath() + "\"",
|
||||
"-master_pl_name \"playlist.m3u8\""
|
||||
);
|
||||
|
||||
FFmpeg ffmpeg = new FFmpeg();
|
||||
ffmpeg.setLogLevel(FFmpegConstants.FFmpegLogLevel.ERROR);
|
||||
ffmpeg.addInput(ffmpegInput);
|
||||
ffmpeg.addOutput(ffmpegOutput);
|
||||
String cmdParams = ffmpeg.buildCommand();
|
||||
|
||||
// ----------------------------------
|
||||
// Execute command
|
||||
// ----------------------------------
|
||||
|
||||
File cmdPath = OSALBinaryManager.getPath(FFMPEG_BINARY_PATH, "ffmpeg");
|
||||
|
||||
String cmd = cmdPath.getParent() + File.separator + cmdParams;
|
||||
logger.finest("Executing ffmpeg: " + cmd);
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
public void run() {
|
||||
if (process != null) process.destroyForcibly();
|
||||
}
|
||||
});
|
||||
|
||||
process = Runtime.getRuntime().exec(cmd);
|
||||
BufferedReader output = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
||||
|
||||
while (process.isAlive()) {
|
||||
String line;
|
||||
while ((line = output.readLine()) != null) {
|
||||
logger.finest("[Cam: " + camera.getRtspUrl() + "] " + line);
|
||||
}
|
||||
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
output.close();
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "RTSP Stream recording thread has crashed for: " + camera.getRtspUrl(), e);
|
||||
logger.log(Level.SEVERE, "RTSP Stream recording thread has crashed for: " + camera.getRtspUrl(), e);
|
||||
} finally {
|
||||
logger.info("Shutting down RTSP Stream recording thread for: " + camera.getRtspUrl());
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
camera = null;
|
||||
if (process != null) {
|
||||
logger.info("Killing ffmpeg instance.");
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
package se.hal.plugin.nvr.rtsp;
|
||||
|
||||
import se.hal.HalContext;
|
||||
import se.hal.intf.HalDeviceConfig;
|
||||
import se.hal.intf.HalDeviceReportListener;
|
||||
import se.hal.plugin.nvr.intf.HalCameraController;
|
||||
|
|
@ -31,7 +32,7 @@ import zutil.log.LogUtil;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
|
|
@ -40,7 +41,8 @@ public class RTSPController implements HalCameraController {
|
|||
|
||||
private static final String CONFIG_RECORDING_PATH = "nvr.recording_path";
|
||||
|
||||
private List<RTSPCameraConfig> cameras = new ArrayList<>();
|
||||
private static ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||
private List<RTSPCameraRecorder> recorders = new ArrayList<>();
|
||||
private List<HalDeviceReportListener> deviceListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
|
||||
|
|
@ -67,18 +69,28 @@ public class RTSPController implements HalCameraController {
|
|||
|
||||
@Override
|
||||
public void register(HalDeviceConfig deviceConfig) {
|
||||
if (deviceConfig instanceof RTSPCameraConfig)
|
||||
cameras.add((RTSPCameraConfig) deviceConfig);
|
||||
if (deviceConfig instanceof RTSPCameraConfig) {
|
||||
RTSPCameraConfig rtspCam = (RTSPCameraConfig) deviceConfig;
|
||||
RTSPCameraRecorder recorder = new RTSPCameraRecorder(rtspCam,
|
||||
HalContext.getStringProperty(CONFIG_RECORDING_PATH, HalContext.RESOURCE_WEB_ROOT + "/recordings/" + deviceConfig.hashCode()));
|
||||
|
||||
recorders.add(recorder);
|
||||
threadPool.execute(recorder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deregister(HalDeviceConfig deviceConfig) {
|
||||
cameras.remove(deviceConfig);
|
||||
RTSPCameraRecorder recorder = getRecorder((RTSPCameraConfig) deviceConfig);
|
||||
if (recorder != null) {
|
||||
recorder.close();
|
||||
recorders.remove(recorder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return cameras.size();
|
||||
return recorders.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -87,4 +99,12 @@ public class RTSPController implements HalCameraController {
|
|||
deviceListeners.add(listener);
|
||||
}
|
||||
|
||||
private RTSPCameraRecorder getRecorder(RTSPCameraConfig cameraConfig) {
|
||||
for (RTSPCameraRecorder recorder : recorders) {
|
||||
if (recorder.getCamera().equals(cameraConfig))
|
||||
return recorder;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
|
||||
package se.hal.plugin.nvr.struct;
|
||||
|
||||
import se.hal.intf.HalAbstractController;
|
||||
import se.hal.intf.HalAbstractDevice;
|
||||
import se.hal.plugin.nvr.intf.HalCameraConfig;
|
||||
import se.hal.plugin.nvr.intf.HalCameraData;
|
||||
|
|
@ -34,7 +33,7 @@ import zutil.db.bean.DBBean;
|
|||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@DBBean.DBTable(value="camera", superBean=true)
|
||||
public class Camera extends HalAbstractDevice<Camera, HalCameraConfig, HalCameraData> {
|
||||
|
||||
public static List<Camera> getCameras(DBConnection db) throws SQLException{
|
||||
|
|
@ -46,6 +45,10 @@ public class Camera extends HalAbstractDevice<Camera, HalCameraConfig, HalCamera
|
|||
}
|
||||
|
||||
|
||||
public String getPlaylistRelativeUrl() {
|
||||
return "recordings/" + getDeviceConfig().hashCode() + "/playlist.m3u8";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HalCameraData getLatestDeviceData(DBConnection db) {
|
||||
return null;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue