From 825385aa54269e9ad3727d07a8642e58ae970802 Mon Sep 17 00:00:00 2001 From: nasseeder1 Date: Mon, 11 Jun 2018 16:05:09 +0000 Subject: [PATCH] 49_SSCam: V5.0.0, HLS Streaming implemented, new Streamingdevice based on module 49_SSCamSTRM, some improvements & fixes git-svn-id: https://svn.fhem.de/fhem/trunk@16850 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 2 + fhem/FHEM/49_SSCam.pm | 760 +++++++++++++++++++++++++++----------- fhem/FHEM/49_SSCamSTRM.pm | 30 +- 3 files changed, 564 insertions(+), 228 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 0f7fda544..d78001124 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 49_SSCam: V5.0.0, HLS Streaming implemented, new Streamingdevice + based on module 49_SSCamSTRM, some improvements & fixes - feature: 98_Text2Speech: added Duch language for Google TTS - feature: 74_UnifiSwitch: initial version - feature: 74_Unifi: new child-Module UnifiSwitch diff --git a/fhem/FHEM/49_SSCam.pm b/fhem/FHEM/49_SSCam.pm index 4f394c25f..02ddc49a5 100644 --- a/fhem/FHEM/49_SSCam.pm +++ b/fhem/FHEM/49_SSCam.pm @@ -1,4 +1,4 @@ -######################################################################################################################### +######################################################################################################################## # $Id$ ######################################################################################################################### # 49_SSCam.pm @@ -27,6 +27,8 @@ ######################################################################################################################### # Versions History: # +# 5.0.0 11.06.2018 HLS Streaming, Buttons for Streaming-Devices, use of module SSCamSTRM for Streaming-Devices, +# deletion of Streaming-devices if SSCam-device is deleted, some more improvements, minor bugfixes # 4.3.0 27.05.2018 HLS preparation changed # 4.2.0 22.05.2018 PTZ-Panel integrated to created StreamDevice # 4.1.0 05.05.2018 use SYNO.SurveillanceStation.VideoStream instead of SYNO.SurveillanceStation.VideoStreaming, @@ -227,7 +229,7 @@ use Time::HiRes; use HttpUtils; # no if $] >= 5.017011, warnings => 'experimental'; -my $SSCamVersion = "4.3.0"; +my $SSCamVersion = "5.0.0"; # Aufbau Errorcode-Hashes (siehe Surveillance Station Web API) my %SSCam_errauthlist = ( @@ -318,6 +320,7 @@ sub SSCam_Initialize($) { return undef; } +################################################################ sub SSCam_Define($@) { # Die Define-Funktion eines Moduls wird von Fhem aufgerufen wenn der Define-Befehl für ein Gerät ausgeführt wird # Welche und wie viele Parameter akzeptiert werden ist Sache dieser Funktion. Die Werte werden nach dem übergebenen Hash in ein Array aufgeteilt @@ -377,6 +380,7 @@ sub SSCam_Define($@) { $hash->{HELPER}{RECTIME_DEF} = "15"; # Standard für rectime setzen, überschreibbar durch Attribut "rectime" bzw. beim "set .. on-for-time" $hash->{HELPER}{OLDPTZHOME} = ""; $hash->{".ptzhtml"} = ""; + $hash->{HELPER}{HLSSTREAM} = "inactive"; # Aktivitätsstatus HLS-Streaming readingsBeginUpdate($hash); readingsBulkUpdate($hash,"PollState","Inactive"); # es ist keine Gerätepolling aktiv @@ -409,6 +413,7 @@ return undef; sub SSCam_Delete($$) { my ($hash, $arg) = @_; my $index = $hash->{TYPE}."_".$hash->{NAME}."_credentials"; + my $name = $hash->{NAME}; # gespeicherte Credentials löschen setKeyValue($index, undef); @@ -417,6 +422,9 @@ sub SSCam_Delete($$) { my $sgdev = "SSCam.$hash->{NAME}.snapgallery"; CommandDelete($hash->{CL},"$sgdev"); + # alle Streaming-Devices löschen falls vorhanden + CommandDelete($hash->{CL},"TYPE=SSCamSTRM:FILTER=PARENT=$name"); + return undef; } @@ -595,9 +603,10 @@ sub SSCam_Set($@) { my @prop; return "module is deactivated" if(IsDisabled($name)); - + if(SSCam_IsModelCam($hash)) { # selist für Cams + my $hlslfw = (ReadingsVal($name,"CamStreamFormat","MJPEG") eq "HLS")?",live_fw_hls,":","; $setlist = "Unknown argument $opt, choose one of ". "credentials ". ((ReadingsVal("$name", "CapPTZPan", "false") ne "false") ? "delPreset:".ReadingsVal("$name","Presets","")." " : ""). @@ -613,7 +622,7 @@ sub SSCam_Set($@) { "enable:noArg ". "disable:noArg ". "optimizeParams ". - "runView:live_fw,live_link,live_open,lastrec_fw,lastrec_fw_MJPEG,lastrec_fw_MPEG4/H.264,lastrec_open,lastsnap_fw ". + "runView:live_fw".$hlslfw."live_link,live_open,lastrec_fw,lastrec_fw_MJPEG,lastrec_fw_MPEG4/H.264,lastrec_open,lastsnap_fw ". ((ReadingsVal("$name", "CapPTZPan", "false") ne "false") ? "setPreset ": ""). ((ReadingsVal("$name", "CapPTZPan", "false") ne "false") ? "setHome:---currentPosition---,".ReadingsVal("$name","Presets","")." " : ""). "stopView:noArg ". @@ -693,44 +702,43 @@ sub SSCam_Set($@) { } elsif ($opt eq "createSnapGallery" && SSCam_IsModelCam($hash)) { if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} my ($ret,$sgdev); - return "When you want use \"$opt\", you have to set the attribute \"snapGalleryBoost\" first because the functionality depends on retrieving snapshots automatically." + return "Before use \"$opt\" you have to set the attribute \"snapGalleryBoost\" first due to the technology of retrieving snapshots automatically is needed." if(!AttrVal($name,"snapGalleryBoost",0)); - $sgdev = "SSCam.$name.snapgallery"; - $ret = CommandDefine($hash->{CL},"$sgdev weblink htmlCode {composegallery('$name','$sgdev')}"); + $sgdev = "SSCamSTRM.$name.snapgallery"; + $ret = CommandDefine($hash->{CL},"$sgdev SSCamSTRM {composegallery('$name','$sgdev','snapgallery')}"); return $ret if($ret); - my $wlname = "SSCam.$name.snapgallery"; - my $room = "SnapGallery"; - CommandAttr($hash->{CL},$wlname." room ".$room); - return "Snapgallery device \"$sgdev\" was created successfully. Please have a look to room $room.
You can now assign it to another room if you want. Don't rename this device !"; + my $room = "SnapGallery"; + $attr{$sgdev}{room} = $room; + return "Snapgallery device \"$sgdev\" created and assigned to room \"$room\"."; } elsif ($opt eq "createPTZcontrol" && SSCam_IsModelCam($hash)) { if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} - my $ptzcdev = "SSCam.$name.PTZcontrol"; - my $ret = CommandDefine($hash->{CL},"$ptzcdev weblink htmlCode {SSCam_ptzpanel('$name')}"); + my $ptzcdev = "SSCamSTRM.$name.PTZcontrol"; + my $ret = CommandDefine($hash->{CL},"$ptzcdev SSCamSTRM {SSCam_ptzpanel('$name','$ptzcdev','ptzcontrol')}"); return $ret if($ret); my $room = AttrVal($name,"room","PTZcontrol"); $attr{$ptzcdev}{room} = $room; $attr{$ptzcdev}{group} = $name."_PTZcontrol"; - return "PTZ control device \"$ptzcdev\" was created successfully. Please have a look to room \"$room\".\nYou can assign \"$ptzcdev\" to another room if you want."; + return "PTZ control device \"$ptzcdev\" created and assigned to room \"$room\"."; } elsif ($opt eq "createStreamDev" && SSCam_IsModelCam($hash)) { if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} my ($livedev,$ret); if($prop =~ /mjpeg/) { - $livedev = "SSCam.$name.mjpeg"; - $ret = CommandDefine($hash->{CL},"$livedev weblink htmlCode {SSCam_StreamDev('$name','$livedev','mjpeg')}"); + $livedev = "SSCamSTRM.$name.mjpeg"; + $ret = CommandDefine($hash->{CL},"$livedev SSCamSTRM {SSCam_StreamDev('$name','$livedev','mjpeg')}"); return $ret if($ret); } if($prop =~ /switched/) { - $livedev = "SSCam.$name.switched"; - $ret = CommandDefine($hash->{CL},"$livedev weblink htmlCode {SSCam_StreamDev('$name','$livedev','switched')}"); + $livedev = "SSCamSTRM.$name.switched"; + $ret = CommandDefine($hash->{CL},"$livedev SSCamSTRM {SSCam_StreamDev('$name','$livedev','switched')}"); return $ret if($ret); } - my $room = AttrVal($name,"room","PTZcontrol"); + my $room = AttrVal($name,"room","Livestream"); $attr{$livedev}{room} = $room; - return "Livestream device \"$livedev\" was created successfully. Please have a look to room \"$room\".\nYou can assign \"$livedev\" to another room if you want but please don't rename the device."; + return "Livestream device \"$livedev\" created and assigned to room \"$room\"."; } elsif ($opt eq "enable" && SSCam_IsModelCam($hash)) { if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} @@ -920,7 +928,7 @@ sub SSCam_Set($@) { } elsif ($prop eq "live_fw_hls") { $hash->{HELPER}{OPENWINDOW} = 0; $hash->{HELPER}{WLTYPE} = "hls"; - $hash->{HELPER}{ALIAS} = "View on Safari"; + $hash->{HELPER}{ALIAS} = "View only on compatible browsers"; $hash->{HELPER}{RUNVIEW} = "live_fw_hls"; } elsif ($prop eq "lastsnap_fw") { $hash->{HELPER}{OPENWINDOW} = 0; @@ -932,6 +940,16 @@ sub SSCam_Set($@) { } SSCam_runliveview($hash); + } elsif ($opt eq "hlsreactivate" && SSCam_IsModelCam($hash)) { + # ohne SET-Menüeintrag + if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} + SSCam_hlsreactivate($hash); + + } elsif ($opt eq "hlsactivate" && SSCam_IsModelCam($hash)) { + # ohne SET-Menüeintrag + if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} + SSCam_hlsactivate($hash); + } elsif ($opt eq "extevent" && !SSCam_IsModelCam($hash)) { if (!$hash->{CREDENTIALS}) {return "Credentials of $name are not set - make sure you've set it with \"set $name credentials username password\"";} @@ -1132,30 +1150,30 @@ sub SSCam_FWsummaryFn ($$$$) { return if(!$hash->{HELPER}{LINK} || ReadingsVal($d, "state", "") =~ /^dis.*/ || IsDisabled($name)); my $attr = AttrVal($d, "htmlattr", " "); - Log3($name, 4, "$name - SSCam_FWsummaryFn called - FW_wname: $FW_wname, device: $d, room: $room, attributes: $attr, FwDetail: ".weblink_FwDetail($d)); + Log3($name, 4, "$name - SSCam_FWsummaryFn called - FW_wname: $FW_wname, device: $d, room: $room, attributes: $attr"); if($wltype eq "image") { - $ret = "
".weblink_FwDetail($d); + $ret = "
"; if($hash->{HELPER}{AUDIOLINK} && ReadingsVal($d, "CamAudioType", "Unknown") !~ /Unknown/) { $ret .= "".weblink_FwDetail($d); + "; } } elsif($wltype eq "iframe") { - $ret = "".weblink_FwDetail($d); + $ret = ""; if($hash->{HELPER}{AUDIOLINK} && ReadingsVal($d, "CamAudioType", "Unknown") !~ /Unknown/) { $ret .= "".weblink_FwDetail($d); + "; } } elsif($wltype eq "embed") { - $ret = "".weblink_FwDetail($d); + $ret = ""; if($hash->{HELPER}{AUDIOLINK} && ReadingsVal($d, "CamAudioType", "Unknown") !~ /Unknown/) { $ret .= "".weblink_FwDetail($d); + "; } } elsif($wltype eq "link") { @@ -1168,8 +1186,9 @@ sub SSCam_FWsummaryFn ($$$$) { } elsif($wltype eq "hls") { $alias = $hash->{HELPER}{ALIAS}; - $ret = "".weblink_FwDetail($d); + "; if($hash->{HELPER}{AUDIOLINK} && ReadingsVal($d, "CamAudioType", "Unknown") !~ /Unknown/) { $ret .= "".weblink_FwDetail($d); + "; } } -#FW_directNotify("FILTER=room=$room", "#FHEMWEB:$FW_wname", "location.reload('true')", "") if($d eq $name); + return $ret; } @@ -1206,7 +1223,6 @@ sub SSCam_FWdetailFn ($$$$) { } else { return undef; } - } ###################################################################################### @@ -2019,6 +2035,102 @@ sub SSCam_runliveview($) { } } +############################################################################### +# Kamera HLS-Stream aktivieren +############################################################################### +sub SSCam_hlsactivate($) { + my ($hash) = @_; + my $camname = $hash->{CAMNAME}; + my $name = $hash->{NAME}; + my $errorcode; + my $error; + + RemoveInternalTimer($hash, "SSCam_hlsactivate"); + return if(IsDisabled($name)); + + if (ReadingsVal("$name", "state", "") =~ /^dis.*/) { + if (ReadingsVal("$name", "state", "") eq "disabled") { + $errorcode = "402"; + } elsif (ReadingsVal("$name", "state", "") eq "disconnected") { + $errorcode = "502"; + } + + # Fehlertext zum Errorcode ermitteln + $error = &SSCam_experror($hash,$errorcode); + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"Errorcode",$errorcode); + readingsBulkUpdate($hash,"Error",$error); + readingsEndUpdate($hash, 1); + + Log3($name, 2, "$name - ERROR - HLS-Stream of Camera $camname can't be activated - $error"); + return; + } + + if ($hash->{HELPER}{ACTIVE} eq "off") { + # Aktivierung starten + $hash->{OPMODE} = "activate_hls"; + $hash->{HELPER}{ACTIVE} = "on"; + $hash->{HELPER}{LOGINRETRIES} = 0; + + if (AttrVal($name,"debugactivetoken",0)) { + Log3($name, 3, "$name - Active-Token was set by OPMODE: $hash->{OPMODE}"); + } + SSCam_getapisites($hash); + + } else { + InternalTimer(gettimeofday()+0.3, "SSCam_hlsactivate", $hash, 0); + } +} + +############################################################################### +# HLS-Stream reaktivieren (stoppen & starten) +############################################################################### +sub SSCam_hlsreactivate($) { + my ($hash) = @_; + my $camname = $hash->{CAMNAME}; + my $name = $hash->{NAME}; + my $errorcode; + my $error; + + RemoveInternalTimer($hash, "SSCam_hlsreactivate"); + return if(IsDisabled($name)); + + if (ReadingsVal("$name", "state", "") =~ /^dis.*/) { + if (ReadingsVal("$name", "state", "") eq "disabled") { + $errorcode = "402"; + } elsif (ReadingsVal("$name", "state", "") eq "disconnected") { + $errorcode = "502"; + } + + # Fehlertext zum Errorcode ermitteln + $error = &SSCam_experror($hash,$errorcode); + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"Errorcode",$errorcode); + readingsBulkUpdate($hash,"Error",$error); + readingsEndUpdate($hash, 1); + + Log3($name, 2, "$name - ERROR - HLS-Stream of Camera $camname can't be activated - $error"); + return; + } + + if ($hash->{HELPER}{ACTIVE} eq "off") { + # Aktivierung starten + $hash->{OPMODE} = "reactivate_hls"; + $hash->{HELPER}{ACTIVE} = "on"; + $hash->{HELPER}{LOGINRETRIES} = 0; + + if (AttrVal($name,"debugactivetoken",0)) { + Log3($name, 3, "$name - Active-Token was set by OPMODE: $hash->{OPMODE}"); + } + SSCam_getapisites($hash); + + } else { + InternalTimer(gettimeofday()+0.4, "SSCam_hlsreactivate", $hash, 0); + } +} + ############################################################################### # Kamera Liveview stoppen ############################################################################### @@ -2049,16 +2161,20 @@ sub SSCam_stopliveview ($) { # Reading LiveStreamUrl löschen delete($defs{$name}{READINGS}{LiveStreamUrl}) if ($defs{$name}{READINGS}{LiveStreamUrl}); - # Longpoll refresh - readingsSingleUpdate($hash,"state","stopview",1); - - # Aufnahmestatus im state abbilden - my $st = (ReadingsVal("$name", "Record", "Stop") eq "Start")?"on":"off"; - readingsSingleUpdate($hash,"state", $st, 1); - $hash->{HELPER}{ACTIVE} = "off"; - if (AttrVal($name,"debugactivetoken",0)) { - Log3($name, 3, "$name - Active-Token deleted by OPMODE: $hash->{OPMODE}"); + readingsSingleUpdate($hash,"state","stopview",1); + + if($hash->{HELPER}{WLTYPE} eq "hls") { + # HLS Stream war aktiv, Streaming beenden + $hash->{OPMODE} = "stopliveview_hls"; + SSCam_getapisites($hash); + } else { + # kein HLS Stream + SSCam_refresh($hash,1,1); # Room-Refresh, Longpoll + $hash->{HELPER}{ACTIVE} = "off"; + if (AttrVal($name,"debugactivetoken",0)) { + Log3($name, 3, "$name - Active-Token deleted by OPMODE: $hash->{OPMODE}"); + } } } else { @@ -2305,24 +2421,27 @@ sub SSCam_getcaminfoall ($$) { # Model ist CAM RemoveInternalTimer($hash, "SSCam_geteventlist"); InternalTimer(gettimeofday()+0.5, "SSCam_geteventlist", $hash, 0); + RemoveInternalTimer($hash, "SSCam_getmotionenum"); + InternalTimer(gettimeofday()+0.6, "SSCam_getmotionenum", $hash, 0); RemoveInternalTimer($hash, "SSCam_getcaminfo"); InternalTimer(gettimeofday()+0.9, "SSCam_getcaminfo", $hash, 0); - + RemoveInternalTimer($hash, "SSCam_getcapabilities"); + InternalTimer(gettimeofday()+1.3, "SSCam_getcapabilities", $hash, 0); + RemoveInternalTimer($hash, "SSCam_getstreamformat"); + InternalTimer(gettimeofday()+1.4, "SSCam_getstreamformat", $hash, 0); + # Schnappschußgalerie abrufen (snapGalleryBoost) oder nur Info des letzten Snaps my ($slim,$ssize) = SSCam_snaplimsize($hash); RemoveInternalTimer("SSCam_getsnapinfo"); InternalTimer(gettimeofday()+1.5, "SSCam_getsnapinfo", "$name:$slim:$ssize", 0); - RemoveInternalTimer($hash, "SSCam_getmotionenum"); - InternalTimer(gettimeofday()+0.6, "SSCam_getmotionenum", $hash, 0); - RemoveInternalTimer($hash, "SSCam_getcapabilities"); - InternalTimer(gettimeofday()+1.3, "SSCam_getcapabilities", $hash, 0); RemoveInternalTimer($hash, "SSCam_getptzlistpreset"); InternalTimer(gettimeofday()+1.6, "SSCam_getptzlistpreset", $hash, 0); RemoveInternalTimer($hash, "SSCam_getptzlistpatrol"); InternalTimer(gettimeofday()+1.9, "SSCam_getptzlistpatrol", $hash, 0); RemoveInternalTimer($hash, "SSCam_getStmUrlPath"); InternalTimer(gettimeofday()+2.1, "SSCam_getStmUrlPath", $hash, 0); + } else { # Model ist SVS RemoveInternalTimer($hash, "SSCam_gethomemodestate"); @@ -2595,6 +2714,32 @@ sub SSCam_getcaminfo ($) { } } +########################################################################### +# SYNO.SurveillanceStation.VideoStream query aktuelles Streamformat +########################################################################### +sub SSCam_getstreamformat ($) { + my ($hash) = @_; + my $camname = $hash->{CAMNAME}; + my $name = $hash->{NAME}; + + RemoveInternalTimer($hash, "SSCam_getstreamformat"); + return if(IsDisabled($name)); + + if ($hash->{HELPER}{ACTIVE} eq "off") { + $hash->{OPMODE} = "getstreamformat"; + $hash->{HELPER}{ACTIVE} = "on"; + $hash->{HELPER}{LOGINRETRIES} = 0; + + if (AttrVal($name,"debugactivetoken",0)) { + Log3($name, 3, "$name - Active-Token was set by OPMODE: $hash->{OPMODE}"); + } + SSCam_getapisites($hash); + + } else { + InternalTimer(gettimeofday()+1.4, "SSCam_getstreamformat", $hash, 0); + } +} + ################################################################################ # Kamera Stream Urls abrufen (Get) ################################################################################ @@ -3444,7 +3589,7 @@ sub SSCam_camop ($) { my $sid = $hash->{HELPER}{SID}; my $OpMode = $hash->{OPMODE}; my $camid = $hash->{CAMID}; - my ($livestream,$winname,$attr,$room,$param); + my ($exturl,$winname,$attr,$room,$param); my ($url,$snapid,$httptimeout,$expmode,$motdetsc); Log3($name, 4, "$name - --- Begin Function $OpMode nonblocking ---"); @@ -3705,20 +3850,25 @@ sub SSCam_camop ($) { } elsif ($OpMode eq "runliveview" && $hash->{HELPER}{RUNVIEW} !~ m/snap|^live_.*hls$/) { if ($hash->{HELPER}{RUNVIEW} =~ m/live/) { $hash->{HELPER}{AUDIOLINK} = "http://$serveraddr:$serverport/webapi/$apiaudiostmpath?api=$apiaudiostm&version=$apiaudiostmmaxver&method=Stream&cameraId=$camid&_sid=$sid"; - # externe URL - $livestream = AttrVal($name, "livestreamprefix", "http://$serveraddr:$serverport"); - $livestream .= "/webapi/$apivideostmspath?api=$apivideostms&version=$apivideostmsmaxver&method=Stream&cameraId=$camid&format=mjpeg&_sid=$sid"; - readingsSingleUpdate($hash,"LiveStreamUrl", $livestream, 1) if(AttrVal($name, "showStmInfoFull", undef)); + # externe URL in Reading setzen + $exturl = AttrVal($name, "livestreamprefix", "http://$serveraddr:$serverport"); + $exturl .= "/webapi/$apivideostmspath?api=$apivideostms&version=$apivideostmsmaxver&method=Stream&cameraId=$camid&format=mjpeg&_sid=$sid"; + readingsSingleUpdate($hash,"LiveStreamUrl", $exturl, 1) if(AttrVal($name, "showStmInfoFull", undef)); # interne URL $url = "http://$serveraddr:$serverport/webapi/$apivideostmspath?api=$apivideostms&version=$apivideostmsmaxver&method=Stream&cameraId=$camid&format=mjpeg&_sid=$sid"; } else { - # Abspielen der letzten Aufnahme (EventId) + # Abspielen der letzten Aufnahme (EventId) + # externe URL in Reading setzen + $exturl = AttrVal($name, "livestreamprefix", "http://$serveraddr:$serverport"); + $exturl .= "/webapi/$apistmpath?api=$apistm&version=$apistmmaxver&method=EventStream&eventId=$hash->{HELPER}{CAMLASTRECID}×tamp=1&_sid=$sid"; + readingsSingleUpdate($hash,"LiveStreamUrl", $exturl, 1) if(AttrVal($name, "showStmInfoFull", undef)); + # interne URL $url = "http://$serveraddr:$serverport/webapi/$apistmpath?api=$apistm&version=$apistmmaxver&method=EventStream&eventId=$hash->{HELPER}{CAMLASTRECID}×tamp=1&_sid=$sid"; } # Liveview-Link in Hash speichern $hash->{HELPER}{LINK} = $url; - + Log3($name, 4, "$name - Set Streaming-URL: $url"); # livestream sofort in neuem Browsertab öffnen @@ -3734,13 +3884,10 @@ sub SSCam_camop ($) { map {FW_directNotify("#FHEMWEB:$_", "window.open ('$url','$winname','$attr')", "")} devspec2array("WEB.*"); } } + + SSCam_refresh($hash,1,1); # Room-Refresh, Longpoll - # Aufnahmestatus in state abbilden mit Longpoll refresh - my $st; - (ReadingsVal("$name", "Record", "") eq "Start")?$st="on":$st="off"; - readingsSingleUpdate($hash,"state", $st, 1); - - $hash->{HELPER}{ACTIVE} = "off"; + $hash->{HELPER}{ACTIVE} = "off"; if (AttrVal($name,"debugactivetoken",0)) { Log3($name, 3, "$name - Active-Token deleted by OPMODE: $hash->{OPMODE}"); } @@ -3753,10 +3900,18 @@ sub SSCam_camop ($) { my $keyword = $hash->{CAMNAME}; # nur Snaps von $camname selektieren, für lastsnap_fw $url = "http://$serveraddr:$serverport/webapi/$apitakesnappath?api=\"$apitakesnap\"&method=\"List\"&version=\"$apitakesnapmaxver\"&keyword=\"$keyword\"&imgSize=\"$imgsize\"&limit=\"$limit\"&_sid=\"$sid\""; - } elsif ($OpMode eq "runliveview" && $hash->{HELPER}{RUNVIEW} =~ m/^live_.*hls$/) { + } elsif (($OpMode eq "runliveview" && $hash->{HELPER}{RUNVIEW} =~ m/^live_.*hls$/) || $OpMode eq "activate_hls") { # HLS Livestreaming aktivieren $httptimeout = $httptimeout+90; # aktivieren HLS dauert lange ! $url = "http://$serveraddr:$serverport/webapi/$apivideostmspath?api=$apivideostms&version=$apivideostmsmaxver&method=Open&cameraId=$camid&format=hls&_sid=$sid"; + + } elsif ($OpMode eq "stopliveview_hls" || $OpMode eq "reactivate_hls") { + # HLS Livestreaming deaktivieren + $url = "http://$serveraddr:$serverport/webapi/$apivideostmspath?api=$apivideostms&version=$apivideostmsmaxver&method=Close&cameraId=$camid&format=hls&_sid=$sid"; + + } elsif ($OpMode eq "getstreamformat") { + # aktuelles Streamformat abfragen + $url = "http://$serveraddr:$serverport/webapi/$apivideostmspath?api=$apivideostms&version=$apivideostmsmaxver&method=Query&cameraId=$camid&_sid=$sid"; } Log3($name, 4, "$name - Call-Out now: $url"); @@ -3827,7 +3982,7 @@ sub SSCam_camop_parse ($) { } elsif ($myjson ne "") { # wenn die Abfrage erfolgreich war ($data enthält die Ergebnisdaten des HTTP Aufrufes) # Evaluiere ob Daten im JSON-Format empfangen wurden - ($hash, $success) = SSCam_evaljson($hash,$myjson); + ($hash,$success,$myjson) = SSCam_evaljson($hash,$myjson); unless ($success) { Log3($name, 4, "$name - Data returned: ".$myjson); @@ -4142,14 +4297,13 @@ sub SSCam_camop_parse ($) { if (exists($hash->{HELPER}{RUNVIEW}) && $hash->{HELPER}{RUNVIEW} =~ /snap/ && exists($data->{'data'}{'data'}[0]{imageData})) { delete $hash->{HELPER}{RUNVIEW}; - # Aufnahmestatus in state abbilden + # Aufnahmestatus in state abbilden my $st; (ReadingsVal("$name", "Record", "") eq "Start")?$st="on":$st="off"; - readingsSingleUpdate($hash,"state", $st, 1); + readingsSingleUpdate($hash,"state", $st, 0); $hash->{HELPER}{LINK} = $data->{data}{data}[0]{imageData}; - # Longpoll refresh - DoTrigger($name,"startview"); + SSCam_refresh($hash,1,1); # Page Refresh, Longpoll } if($OpMode eq "getsnapgallery") { @@ -4201,17 +4355,44 @@ sub SSCam_camop_parse ($) { Log3($name, $verbose, "$name - Snapinfos of camera $camname retrieved"); } elsif ($OpMode eq "runliveview" && $hash->{HELPER}{RUNVIEW} =~ m/^live_.*hls$/) { - # HLS Streaming - my $url = "http://$serveraddr:$serverport/webapi/$apivideostmspath?api=$apivideostms&version=$apivideostmsmaxver&method=Stream&cameraId=$camid&format=hls&_sid=$sid"; - # Liveview-Link in Hash speichern - $hash->{HELPER}{LINK} = $url; - Log3($name, 4, "$name - HLS Streaming of camera \"$name\" activated, Streaming-URL: $url"); - - # Aufnahmestatus in state abbilden mit Longpoll refresh - my $st; - (ReadingsVal("$name", "Record", "") eq "Start")?$st="on":$st="off"; - readingsSingleUpdate($hash,"state", $st, 1); + # HLS Streaming wurde aktiviert + $hash->{HELPER}{HLSSTREAM} = "active"; + # externe LivestreamURL setzen + my $exturl = AttrVal($name, "livestreamprefix", "http://$serveraddr:$serverport"); + $exturl .= "/webapi/$apivideostmspath?api=$apivideostms&version=$apivideostmsmaxver&method=Stream&cameraId=$camid&format=hls&_sid=$sid"; + readingsSingleUpdate($hash,"LiveStreamUrl", $exturl, 1) if(AttrVal($name, "showStmInfoFull", undef)); + my $url = "http://$serveraddr:$serverport/webapi/$apivideostmspath?api=$apivideostms&version=$apivideostmsmaxver&method=Stream&cameraId=$camid&format=hls&_sid=$sid"; + # Liveview-Link in Hash speichern und Aktivitätsstatus speichern + $hash->{HELPER}{LINK} = $url; + Log3($name, 4, "$name - HLS Streaming of camera \"$name\" activated, Streaming-URL: $url") if(AttrVal($name,"verbose",3) == 4); + Log3($name, 3, "$name - HLS Streaming of camera \"$name\" activated") if(AttrVal($name,"verbose",3) == 3); + + SSCam_refresh($hash,1,1); # Page Refresh, Longpoll + + } elsif ($OpMode eq "stopliveview_hls") { + # HLS Streaming wurde deaktiviert, Aktivitätsstatus speichern + $hash->{HELPER}{HLSSTREAM} = "inactive"; + Log3($name, 3, "$name - HLS Streaming of camera \"$name\" deactivated"); + + SSCam_refresh($hash,1,1); # Page Refresh, Longpoll + + } elsif ($OpMode eq "reactivate_hls") { + # HLS Streaming wurde deaktiviert, Aktivitätsstatus speichern + $hash->{HELPER}{HLSSTREAM} = "inactive"; + Log3($name, 4, "$name - HLS Streaming of camera \"$name\" deactivated for streaming device"); + + # Token freigeben vor hlsactivate + $hash->{HELPER}{ACTIVE} = "off"; + SSCam_hlsactivate($hash); + + SSCam_refresh($hash,1,1); # Page Refresh, Longpoll + + } elsif ($OpMode eq "activate_hls") { + # HLS Streaming wurde aktiviert, Aktivitätsstatus speichern + $hash->{HELPER}{HLSSTREAM} = "active"; + Log3($name, 4, "$name - HLS Streaming of camera \"$name\" activated for streaming device"); + } elsif ($OpMode eq "getsnapfilename") { # den Filenamen eines Schnapschusses ermitteln $snapid = ReadingsVal("$name", "LastSnapId", " "); @@ -4225,6 +4406,16 @@ sub SSCam_camop_parse ($) { # Logausgabe Log3($name, 4, "$name - Filename of Snap-ID $snapid is \"$data->{'data'}{'data'}[0]{'fileName'}\"") if($data->{'data'}{'data'}[0]{'fileName'}); + } elsif ($OpMode eq "getstreamformat") { + # aktuelles Streamformat abgefragt + my $sformat = SSCam_jboolmap($data->{'data'}->{'format'}); + + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"Errorcode","none"); + readingsBulkUpdate($hash,"Error","none"); + readingsBulkUpdate($hash,"CamStreamFormat", uc($sformat)); + readingsEndUpdate($hash, 1); + } elsif ($OpMode eq "gopreset") { # eine Presetposition wurde angefahren # falls Aufnahme noch läuft -> state = on setzen @@ -5167,21 +5358,67 @@ return; ############################################################################### # Test ob JSON-String empfangen wurde +############################################################################### sub SSCam_evaljson($$) { - my ($hash,$myjson)= @_; + my ($hash,$myjson) = @_; + my $OpMode = $hash->{OPMODE}; + my $name = $hash->{NAME}; my $success = 1; eval {decode_json($myjson)} or do { - $success = 0; - - # Setreading - readingsBeginUpdate($hash); - readingsBulkUpdate($hash,"Errorcode","none"); - readingsBulkUpdate($hash,"Error","malformed JSON string received"); - readingsEndUpdate($hash, 1); + if($hash->{HELPER}{RUNVIEW} =~ m/^live_.*hls$/ || $OpMode =~ m/^.*_hls$/) { + # HLS aktivate/deaktivate bringt kein JSON wenn bereits aktiviert/deaktiviert + Log3($name, 5, "$name - HLS-activation data return: $myjson"); + if ($myjson =~ m/{"success":true}/) { + $success = 1; + $myjson = '{"success":true}'; + } + } else { + $success = 0; + # Setreading + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"Errorcode","none"); + readingsBulkUpdate($hash,"Error","malformed JSON string received"); + readingsEndUpdate($hash, 1); + } }; -return($hash,$success); + +return($hash,$success,$myjson); +} + +############################################################################### +# Refresh eines Raumes aus $hash->{HELPER}{STRMROOM} +# bzw. Longpoll +# $hash, $pload (1=page reload), $longpoll (1=Event) +############################################################################### +sub SSCam_refresh($$$) { + my ($hash,$pload,$longpoll) = @_; + my $name = $hash->{NAME}; + + my $r = $hash->{HELPER}{STRMROOM}?$hash->{HELPER}{STRMROOM}:""; + Log3($name, 5, "$name - room for refresh: $r"); + + if($pload) { + if($hash->{HELPER}{STRMROOM} && $hash->{HELPER}{STRMROOM} !~ /^detail=.*$/) { + # Raumrefresh + my @rooms = split(",",$hash->{HELPER}{STRMROOM}); + foreach (@rooms) { + my $room = $_; + { map { FW_directNotify("FILTER=room=$room", "#FHEMWEB:$_", "location.reload('true')", "") } devspec2array("WEB.*") } + } + } + } + + # Aufnahmestatus in state abbilden & Longpoll + my $st = (ReadingsVal($name, "Record", "") eq "Start")?"on":"off"; + if($longpoll) { + readingsSingleUpdate($hash,"state", $st, 1); + } else { + readingsSingleUpdate($hash,"state", $st, 0); + } + +return; } ############################################################################### @@ -5312,8 +5549,8 @@ return ($ret); # konvertiere alle ptzPanel_rowXX-attribute zu html-Code für # das generierte Widget und das weblink-Device ptzpanel_$name ############################################################################### -sub SSCam_ptzpanel($) { - my ($name) = @_; +sub SSCam_ptzpanel($;$$) { + my ($name,$ptzcdev,$ptzcontrol) = @_; my $hash = $defs{$name}; my $iconpath = AttrVal("$name","ptzPanel_iconPath","www/images/sscam"); my $iconprefix = AttrVal("$name","ptzPanel_iconPrefix","black_btn_"); @@ -5468,13 +5705,17 @@ sub SSCam_StreamDev($$$) { my $sid = $hash->{HELPER}{SID}; my ($cause,$ret,$link,$audiolink,$devWlink,$wlhash,$alias,$wlalias); - my $ha = AttrVal($camname, "htmlattr", 'width="500" height="325"'); + # den Raum/Räume des Streamingdevice speichern für Refresh + $hash->{HELPER}{STRMROOM} = AttrVal($livdev,"room",undef)?AttrVal($livdev,"room",undef):$FW_webArgs{room}; + + my $ha = AttrVal($camname, "htmlattr", 'width="500" height="325"'); # HTML Attribute der Cam + $ha = AttrVal($livdev, "htmlattr", $ha); # htmlattr mit htmattr Streaming-Device übersteuern + my $hlslfw = (ReadingsVal($camname,"CamStreamFormat","MJPEG") eq "HLS")?"live_fw_hls,":undef; my $StmKey = ReadingsVal($camname,"StmKey",undef); $wlalias = AttrVal($livdev, "alias", $livdev); # Linktext als Aliasname oder Devicename setzen $devWlink = "$wlalias"; $wlhash = $defs{$livdev}; - $ha = AttrVal($livdev, "htmlattr", $ha); # htmlattr vom weblink-Device übernehmen oder von Cam # Document Division $ret = sprintf("
$devWlink"); @@ -5485,7 +5726,8 @@ sub SSCam_StreamDev($$$) { if(!$StmKey || ReadingsVal($camname, "Availability", "") ne "enabled" || IsDisabled($camname)) { # Ausgabe bei Fehler my $cam = AttrVal($camname, "alias", $camname); - $cause = !$StmKey?"Camera $cam has no Reading \"StmKey\" set !":"Camera $cam is disabled !"; + $cause = !$StmKey?"Camera $cam has no Reading \"StmKey\" set !":"Camera \"$cam\" is disabled"; + $cause = "Camera \"$cam\" is disabled" if(IsDisabled($camname)); $ret .= "
$cause

"; $ret .= ''; $ret .= ''; @@ -5513,12 +5755,32 @@ sub SSCam_StreamDev($$$) { "; $ret .= ''; } + } elsif($fmt =~ /switched/) { my $wltype = $hash->{HELPER}{WLTYPE}; $link = $hash->{HELPER}{LINK}; - if($link && $wltype =~ /image|iframe|video|base64img|embed/) { + my $cmdstop = "cmd=set $camname stopView"; # Stream deaktivieren + my $imgstop = ""; + my $cmdhlsreact = "cmd=set $camname hlsreactivate"; # HLS Stream reaktivieren + my $imghlsreact = ""; + my $cmdmjpegrun = "cmd=set $camname runView live_fw"; # MJPEG Stream aktivieren + my $imgmjpegrun = ""; + my $cmdhlsrun = "cmd=set $camname runView live_fw_hls"; # HLS Stream aktivieren + my $imghlsrun = ""; + my $cmdlrirun = "cmd=set $camname runView lastrec_fw"; # Last Record IFrame + my $imglrirun = ""; + my $cmdlh264run = "cmd=set $camname runView lastrec_fw_MPEG4/H.264"; # Last Record H.264 + my $imglh264run = ""; + my $cmdlmjpegrun = "cmd=set $camname runView lastrec_fw_MJPEG"; # Last Record MJPEG + my $imglmjpegrun = ""; + my $cmdlsnaprun = "cmd=set $camname runView lastsnap_fw"; # Last SNAP + my $imglsnaprun = ""; + + if($link && $wltype =~ /image|iframe|video|base64img|embed|hls/) { if($wltype =~ /image/) { - $ret .= " "; + $ret .= "
"; + $ret .= "$imgstop "; + $ret .= ""; if(AttrVal($camname,"ptzPanel_use",1)) { my $ptz_ret = SSCam_ptzpanel($camname); if($ptz_ret) { @@ -5529,33 +5791,32 @@ sub SSCam_StreamDev($$$) { $ret .= ''; $ret .= ""; - $ret .= ''; - } + "; + } + } elsif ($wltype =~ /iframe/) { - $ret .= ""; - if(AttrVal($camname,"ptzPanel_use",1)) { - my $ptz_ret = SSCam_ptzpanel($camname); - if($ptz_ret) { - $ret .= "$ptz_ret"; - } - } + $ret .= "
"; + $ret .= "$imgstop "; + $ret .= ""; if($hash->{HELPER}{AUDIOLINK} && ReadingsVal($camname, "CamAudioType", "Unknown") !~ /Unknown/) { + $ret .= ''; $ret .= ''; $ret .= ""; - $ret .= ''; } + } elsif ($wltype =~ /video/) { - $ret .= "
"; + $ret .= "$imgstop "; + $ret .= ""; if($hash->{HELPER}{AUDIOLINK} && ReadingsVal($camname, "CamAudioType", "Unknown") !~ /Unknown/) { $ret .= ''; $ret .= "