| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761 |
- ############################################################################
- # Author: dominik.karall@gmail.com
- # $Id: 98_DLNARenderer.pm 15836 2018-01-09 21:01:49Z dominik $
- #
- # v2.0.7 - 20180108
- # - FEATURE: support ignoredIPs and usedonlyIPs attribute
- #
- # v2.0.6 - 20171209
- # - FEATURE: support acceptedUDNs for UDN whitelisting (thx@MichaelT!)
- # - BUGFIX: fix renew subscriptions errors on offline devices
- # - BUGFIX: fix renew warnings, now only on log level 5 (thx@mumpitzstuff!)
- #
- # v2.0.5 - 20170430
- # - BUGFIX: fix "readings not updated"
- #
- # v2.0.4 - 20170421
- # - FEATURE: support $readingFnAttributes
- # - BUGFIX: fix some freezes
- # - BUGFIX: retry UPnP call 3 times if it fails (error 500)
- #
- # v2.0.3 - 20160918
- # - BUGFIX: fixed SyncPlay for CaskeId players
- #
- # v2.0.2 - 20160913
- # - BUGFIX: fixed pauseToggle (thx@MattG)
- # - BUGFIX: fixed next/previous (thx@MattG)
- #
- # v2.0.1 - 20160725
- # - FEATURE: support DIDL-Lite in channel_X attribute (thx@Weissbrotgrill)
- # - FEATURE: automatically generate DIDL-Lite based on URI (thx@Weissbrotgrill)
- # - CHANGE: update CommandRef perl library requirements
- # - BUGFIX: fix ignoreUDNs crash when device gets removed
- #
- # v2.0.0 - 20160718
- # - CHANGE: first official release within fhem repository
- # - BUGFIX: support device events without / at the end of the xmlns (thx@MichaelT)
- # - FEATURE: support defaultRoom attribute, defines the room to which new devices are assigned
- #
- # v2.0.0 RC5 - 20160614
- # - BUGFIX: support events from devices with wrong serviceId
- # - BUGFIX: fix perl warning on startup
- # - BUGFIX: fix error if LastChange event is empty
- #
- # v2.0.0 RC4 - 20160613
- # - FEATURE: support devices with wrong serviceId
- # - BUGFIX: fix crash during stereo mode update for caskeid players
- # - FEATURE: add stereoPairName reading
- # - CHANGE: add version string to main device internals
- # - BUGFIX: fix error when UPnP method is not implemented
- # - FEATURE: identify stereo support (reading: stereoSupport)
- #
- # v2.0.0 RC3 - 20160609
- # - BUGFIX: check correct number of params for all commands
- # - BUGFIX: fix addUnitToSession/removeUnitFromSession for MUNET/Caskeid devices
- # - BUGFIX: support devices with non-standard UUIDs
- # - CHANGE: use BlockingCall for subscription renewal
- # - CHANGE: remove ignoreUDNs attribute from play devices
- # - CHANGE: remove multiRoomGroups attribute from main device
- # - CHANGE: split stereoDevices reading into stereoLeft/stereoRight
- # - FEATURE: support multiRoomVolume to change volume of all group speakers e.g.
- # set <name> multiRoomVolume +10
- # set <name> multiRoomVolume 25
- # - FEATURE: support channel_01-10 attribute
- # attr <name> channel_01 http://... (save URI to channel_01)
- # set <name> channel 1 (play channel_01)
- # - FEATURE: support speak functionality via Google Translate
- # set <name> speak "This is a test."
- # attr <name> ttsLanguage de
- # set <name> speak "Das ist ein Test."
- # - FEATURE: automatically retrieve stereo mode from speakers and update stereoId/Left/Right readings
- # - FEATURE: support mute
- # set <name> mute on/off
- #
- # v2.0.0 RC2 - 20160510
- # - BUGFIX: fix multiroom for MUNET/Caskeid devices
- #
- # v2.0.0 RC1 - 20160509
- # - CHANGE: change state to offline/playing/stopped/paused/online
- # - CHANGE: removed on/off devstateicon on creation due to changed state values
- # - CHANGE: play is NOT setting AVTransport any more
- # - CHANGE: code cleanup
- # - CHANGE: handle socket via fhem main loop instead of InternalTimer
- # - BUGFIX: do not create new search objects every 30 minutes
- # - FEATURE: support pauseToggle
- # - FEATURE: support SetExtensions (on-for-timer, off-for-timer, ...)
- # - FEATURE: support relative volume changes (e.g. set <device> volume +10)
- #
- # v2.0.0 BETA3 - 20160504
- # - BUGFIX: XML parsing error "NOT_IMPLEMENTED"
- # - CHANGE: change readings to lowcaseUppercase format
- # - FEATURE: support pause
- # - FEATURE: support seek REL_TIME
- # - FEATURE: support next/prev
- #
- # v2.0.0 BETA2 - 20160403
- # - FEATURE: support events from DLNA devices
- # - FEATURE: support caskeid group definitions
- # set <name> saveGroupAs Bad
- # set <name> loadGroup Bad
- # - FEATURE: support caskeid stereo mode
- # set <name> stereo MUNET1 MUNET2 MunetStereoPaar
- # set <name> standalone
- # - CHANGE: use UPnP::ControlPoint from FHEM library
- # - BUGFIX: fix presence status
- #
- # v2.0.0 BETA1 - 20160321
- # - FEATURE: autodiscover and autocreate DLNA devices
- # just use "define dlnadevices DLNARenderer" and wait 2 minutes
- # - FEATURE: support Caskeid (e.g. MUNET devices) with following commands
- # set <name> playEverywhere
- # set <name> stopPlayEverywhere
- # set <name> addUnit <UNIT>
- # set <name> removeUnit <UNIT>
- # set <name> enableBTCaskeid
- # set <name> disableBTCaskeid
- # - FEATURE: display multiroom speakers in multiRoomUnits reading
- # - FEATURE: automatically set alias for friendlyname
- # - FEATURE: automatically set webCmd volume
- # - FEATURE: automatically set devStateIcon audio icons
- # - FEATURE: ignoreUDNs attribute in main
- # - FEATURE: scanInterval attribute in main
- #
- # DLNA Module to play given URLs on a DLNA Renderer
- # and control their volume. Just define
- # define dlnadevices DLNARenderer
- # and look for devices in Unsorted section after 2 minutes.
- #
- #TODO
- # - speak: support continue stream after speak finished
- # - redesign multiroom functionality (virtual devices: represent the readings of master device
- # and send the commands only to the master device (except volume?)
- # automatically create group before playing
- # - use bulk update for readings
- #
- ############################################################################
- package main;
- use strict;
- use warnings;
- use Blocking;
- use SetExtensions;
- use HTML::Entities;
- use XML::Simple;
- use Data::Dumper;
- use Data::UUID;
- use LWP::UserAgent;
- #get UPnP::ControlPoint loaded properly
- my $gPath = '';
- BEGIN {
- $gPath = substr($0, 0, rindex($0, '/'));
- }
- if (lc(substr($0, -7)) eq 'fhem.pl') {
- $gPath = $attr{global}{modpath}.'/FHEM';
- }
- use lib ($gPath.'/lib', $gPath.'/FHEM/lib', './FHEM/lib', './lib', './FHEM', './', '/usr/local/FHEM/share/fhem/FHEM/lib');
- use UPnP::ControlPoint;
- sub DLNARenderer_Initialize($) {
- my ($hash) = @_;
- $hash->{SetFn} = "DLNARenderer_Set";
- $hash->{DefFn} = "DLNARenderer_Define";
- $hash->{ReadFn} = "DLNARenderer_Read";
- $hash->{UndefFn} = "DLNARenderer_Undef";
- $hash->{AttrFn} = "DLNARenderer_Attribute";
- $hash->{AttrList} = "ignoredIPs usedonlyIPs ".$readingFnAttributes;
- }
- sub DLNARenderer_Attribute {
- my ($mode, $devName, $attrName, $attrValue) = @_;
- #ignoreUDNs, multiRoomGroups, channel_01-10
-
- if($mode eq "set") {
-
- } elsif($mode eq "del") {
-
- }
-
- return undef;
- }
- sub DLNARenderer_Define($$) {
- my ($hash, $def) = @_;
- my @param = split("[ \t][ \t]*", $def);
-
- #init caskeid clients for multiroom
- $hash->{helper}{caskeidClients} = "";
- $hash->{helper}{caskeid} = 0;
-
- if(@param < 3) {
- #main
- $hash->{UDN} = 0;
- my $VERSION = "v2.0.7";
- $hash->{VERSION} = $VERSION;
- Log3 $hash, 3, "DLNARenderer: DLNA Renderer $VERSION";
- DLNARenderer_setupControlpoint($hash);
- DLNARenderer_startDlnaRendererSearch($hash);
- readingsSingleUpdate($hash,"state","initialized",1);
- addToDevAttrList($hash->{NAME}, "ignoreUDNs");
- addToDevAttrList($hash->{NAME}, "acceptedUDNs");
- addToDevAttrList($hash->{NAME}, "defaultRoom");
- return undef;
- }
-
- #device specific
- my $name = shift @param;
- my $type = shift @param;
- my $udn = shift @param;
- $hash->{UDN} = $udn;
-
- readingsSingleUpdate($hash,"presence","offline",1);
- readingsSingleUpdate($hash,"state","offline",1);
-
- addToDevAttrList($hash->{NAME}, "multiRoomGroups");
- addToDevAttrList($hash->{NAME}, "ttsLanguage");
- addToDevAttrList($hash->{NAME}, "channel_01");
- addToDevAttrList($hash->{NAME}, "channel_02");
- addToDevAttrList($hash->{NAME}, "channel_03");
- addToDevAttrList($hash->{NAME}, "channel_04");
- addToDevAttrList($hash->{NAME}, "channel_05");
- addToDevAttrList($hash->{NAME}, "channel_06");
- addToDevAttrList($hash->{NAME}, "channel_07");
- addToDevAttrList($hash->{NAME}, "channel_08");
- addToDevAttrList($hash->{NAME}, "channel_09");
- addToDevAttrList($hash->{NAME}, "channel_10");
-
- return undef;
- }
- sub DLNARenderer_Undef($) {
- my ($hash) = @_;
-
- RemoveInternalTimer($hash);
- return undef;
- }
- sub DLNARenderer_Read($) {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $phash = $hash->{phash};
- my $cp = $phash->{helper}{controlpoint};
-
- eval {
- $cp->handleOnce($hash->{CD});
- };
-
- if($@) {
- Log3 $hash, 3, "DLNARenderer: handleOnce failed, $@";
- }
-
- return undef;
- }
- sub DLNARenderer_Set($@) {
- my ($hash, $name, @params) = @_;
- my $dev = $hash->{helper}{device};
-
- # check parameters
- return "no set value specified" if(int(@params) < 1);
- my $ctrlParam = shift(@params);
-
- # check device presence
- if ($ctrlParam ne "?" and (!defined($dev) or ReadingsVal($hash->{NAME}, "presence", "") eq "offline")) {
- return "DLNARenderer: Currently searching for device...";
- }
-
- #get quoted text from params
- my $blankParams = join(" ", @params);
- my @params2;
- while($blankParams =~ /"?((?<!")\S+(?<!")|[^"]+)"?\s*/g) {
- push(@params2, $1);
- }
- @params = @params2;
-
- my $set_method_mapping = {
- volume => {method => \&DLNARenderer_volume, args => 1, argdef => "slider,0,1,100"},
- mute => {method => \&DLNARenderer_mute, args => 1, argdef => "on,off"},
- pause => {method => \&DLNARenderer_upnpPause, args => 0},
- pauseToggle => {method => \&DLNARenderer_pauseToggle, args => 0},
- play => {method => \&DLNARenderer_play, args => 0},
- next => {method => \&DLNARenderer_upnpNext, args => 0},
- previous => {method => \&DLNARenderer_upnpPrevious, args => 0},
- seek => {method => \&DLNARenderer_seek, args => 1},
- multiRoomVolume => {method => \&DLNARenderer_setMultiRoomVolume, args => 1, argdef => "slider,0,1,100", caskeid => 1},
- stereo => {method => \&DLNARenderer_setStereoMode, args => 3, caskeid => 1},
- standalone => {method => \&DLNARenderer_setStandaloneMode, args => 0, caskeid => 1},
- playEverywhere => {method => \&DLNARenderer_playEverywhere, args => 0, caskeid => 1},
- stopPlayEverywhere => {method => \&DLNARenderer_stopPlayEverywhere, args => 0, caskeid => 1},
- addUnit => {method => \&DLNARenderer_addUnit, args => 1, argdef => $hash->{helper}{caskeidClients}, caskeid => 1},
- removeUnit => {method => \&DLNARenderer_removeUnit, args => 1, argdef => ReadingsVal($hash->{NAME}, "multiRoomUnits", ""), caskeid => 1},
- saveGroupAs => {method => \&DLNARenderer_saveGroupAs, args => 1, caskeid => 1},
- enableBTCaskeid => {method => \&DLNARenderer_enableBTCaskeid, args => 0, caskeid => 1},
- disableBTCaskeid => {method => \&DLNARenderer_disableBTCaskeid, args => 0, caskeid => 1},
- off => {method => \&DLNARenderer_upnpStop, args => 0},
- stop => {method => \&DLNARenderer_upnpStop, args => 0},
- loadGroup => {method => \&DLNARenderer_loadGroup, args => 1, caskeid => 1},
- on => {method => \&DLNARenderer_on, args => 0},
- stream => {method => \&DLNARenderer_stream, args => 1},
- channel => {method => \&DLNARenderer_channel, args => 1, argdef => "1,2,3,4,5,6,7,8,9,10"},
- speak => {method => \&DLNARenderer_speak, args => 1}
- };
-
- if($set_method_mapping->{$ctrlParam}) {
- if($set_method_mapping->{$ctrlParam}{args} != int(@params)) {
- return "DLNARenderer: $ctrlParam requires $set_method_mapping->{$ctrlParam}{args} parameter.";
- }
- #params array till args number
- my @args = @params[0 .. $set_method_mapping->{$ctrlParam}{args}];
- $set_method_mapping->{$ctrlParam}{method}->($hash, @args);
- } else {
- my $cmdList;
- foreach my $cmd (keys %$set_method_mapping) {
- next if($hash->{helper}{caskeid} == 0 && $set_method_mapping->{$cmd}{caskeid} && $set_method_mapping->{$cmd}{caskeid} == 1);
- if($set_method_mapping->{$cmd}{args} == 0) {
- $cmdList .= $cmd.":noArg ";
- } else {
- if($set_method_mapping->{$cmd}{argdef}) {
- $cmdList .= $cmd.":".$set_method_mapping->{$cmd}{argdef}." ";
- } else {
- $cmdList .= $cmd." ";
- }
- }
- }
- return SetExtensions($hash, $cmdList, $name, $ctrlParam, @params);
- }
- return undef;
- }
- ##############################
- ##### SET FUNCTIONS ##########
- ##############################
- sub DLNARenderer_speak {
- my ($hash, $ttsText) = @_;
- my $ttsLang = AttrVal($hash->{NAME}, "ttsLanguage", "en");
- return "DLNARenderer: Maximum text length is 100 characters." if(length($ttsText) > 100);
-
- DLNARenderer_stream($hash, "http://translate.google.com/translate_tts?tl=$ttsLang&client=tw-ob&q=$ttsText", "");
- }
- sub DLNARenderer_channel {
- my ($hash, $channelNr) = @_;
- my $stream = AttrVal($hash->{NAME}, sprintf("channel_%02d", $channelNr), "");
- if($stream eq "") {
- return "DLNARenderer: Set channel_XX attribute first.";
- }
- my $meta = "";
- if (substr($stream,0,10) eq "<DIDL-Lite") {
- eval {
- my $xml = XMLin($stream);
- $meta = $stream;
- $stream = $xml->{"item"}{"res"}{"content"};
- };
- if($@) {
- Log3 $hash, 2, "DLNARenderer: Incorrect DIDL-Lite format, $@";
- }
- }
- DLNARenderer_stream($hash, $stream, $meta);
- readingsSingleUpdate($hash, "channel", $channelNr, 1);
- }
- sub DLNARenderer_stream {
- my ($hash, $stream, $meta) = @_;
- if (!defined($meta)) {
- DLNARenderer_generateDidlLiteAndPlay($hash, $stream);
- return undef;
- }
-
- DLNARenderer_upnpSetAVTransportURI($hash, $stream, $meta);
- DLNARenderer_play($hash);
- readingsSingleUpdate($hash, "stream", $stream, 1);
- }
- sub DLNARenderer_on {
- my ($hash) = @_;
- if (defined($hash->{READINGS}{stream})) {
- my $lastStream = $hash->{READINGS}{stream}{VAL};
- if ($lastStream) {
- DLNARenderer_upnpSetAVTransportURI($hash, $lastStream);
- DLNARenderer_play($hash);
- }
- }
- }
- sub DLNARenderer_convertVolumeToAbsolute {
- my ($hash, $targetVolume) = @_;
-
- if(substr($targetVolume, 0, 1) eq "+" or
- substr($targetVolume, 0, 1) eq "-") {
- $targetVolume = ReadingsVal($hash->{NAME}, "volume", 0) + $targetVolume;
- }
- return $targetVolume;
- }
- sub DLNARenderer_volume {
- my ($hash, $targetVolume) = @_;
-
- $targetVolume = DLNARenderer_convertVolumeToAbsolute($hash, $targetVolume);
-
- DLNARenderer_upnpSetVolume($hash, $targetVolume);
- }
- sub DLNARenderer_mute {
- my ($hash, $muteState) = @_;
-
- if($muteState eq "on") {
- $muteState = 1;
- } else {
- $muteState = 0;
- }
-
- DLNARenderer_upnpSetMute($hash, $muteState);
- }
- sub DLNARenderer_removeUnit {
- my ($hash, $unitToRemove) = @_;
- DLNARenderer_removeUnitToPlay($hash, $unitToRemove);
- my $multiRoomUnitsReading = "";
- my @multiRoomUnits = split(",", ReadingsVal($hash->{NAME}, "multiRoomUnits", ""));
-
- foreach my $unit (@multiRoomUnits) {
- $multiRoomUnitsReading .= ",".$unit if($unit ne $unitToRemove);
- }
- $multiRoomUnitsReading = substr($multiRoomUnitsReading, 1) if($multiRoomUnitsReading ne "");
- readingsSingleUpdate($hash, "multiRoomUnits", $multiRoomUnitsReading, 1);
-
- return undef;
- }
- sub DLNARenderer_loadGroup {
- my ($hash, $groupName) = @_;
- my $groupMembers = DLNARenderer_getGroupDefinition($hash, $groupName);
- return "DLNARenderer: Group $groupName not defined." if(!defined($groupMembers));
- DLNARenderer_destroyCurrentSession($hash);
- my $leftSpeaker = "";
- my $rightSpeaker = "";
- my @groupMembersArray = split(",", $groupMembers);
-
- foreach my $member (@groupMembersArray) {
- if($member =~ /^R:([a-zA-Z0-9äöüßÄÜÖ_]+)/) {
- $rightSpeaker = $1;
- } elsif($member =~ /^L:([a-zA-Z0-9äöüßÄÜÖ_]+)/) {
- $leftSpeaker = $1;
- } else {
- DLNARenderer_addUnit($hash, $member);
- }
- }
-
- if($leftSpeaker ne "" && $rightSpeaker ne "") {
- DLNARenderer_setStereoMode($hash, $leftSpeaker, $rightSpeaker, $groupName);
- }
- }
- sub DLNARenderer_stopPlayEverywhere {
- my ($hash) = @_;
- DLNARenderer_destroyCurrentSession($hash);
- readingsSingleUpdate($hash, "multiRoomUnits", "", 1);
- return undef;
- }
- sub DLNARenderer_playEverywhere {
- my ($hash) = @_;
- my $multiRoomUnits = "";
- my @caskeidClients = DLNARenderer_getAllDLNARenderersWithCaskeid($hash);
- foreach my $client (@caskeidClients) {
- if($client->{UDN} ne $hash->{UDN}) {
- DLNARenderer_addUnitToPlay($hash, substr($client->{UDN},5));
-
- my $multiRoomUnits = ReadingsVal($hash->{NAME}, "multiRoomUnits", "");
-
- $multiRoomUnits .= "," if($multiRoomUnits ne "");
- $multiRoomUnits .= ReadingsVal($client->{NAME}, "friendlyName", "");
- readingsSingleUpdate($hash, "multiRoomUnits", $multiRoomUnits, 1);
- }
- }
- return undef;
- }
- sub DLNARenderer_setMultiRoomVolume {
- my ($hash, $targetVolume) = @_;
-
- #change volume of this device
- DLNARenderer_volume($hash, $targetVolume);
-
- #handle volume for all devices in the current group
- #iterate through group and change volume relative to the current volume of this device
- my $mainVolumeDiff = DLNARenderer_convertVolumeToAbsolute($hash, $targetVolume) - ReadingsVal($hash->{NAME}, "volume", 0);
- my $multiRoomUnits = ReadingsVal($hash->{NAME}, "multiRoomUnits", "");
- my @multiRoomUnitsArray = split(",", $multiRoomUnits);
- foreach my $unit (@multiRoomUnitsArray) {
- my $devHash = DLNARenderer_getHashByFriendlyName($hash, $unit);
- my $newVolume = ReadingsVal($devHash->{NAME}, "volume", 0) + $mainVolumeDiff;
- if($newVolume > 100) {
- $newVolume = 100;
- } elsif($newVolume < 0) {
- $newVolume = 0;
- }
- DLNARenderer_volume($devHash, $newVolume);
- }
-
- return undef;
- }
- sub DLNARenderer_pauseToggle {
- my ($hash) = @_;
- if($hash->{READINGS}{state}{VAL} eq "paused") {
- DLNARenderer_play($hash);
- } else {
- DLNARenderer_upnpPause($hash);
- }
- }
- sub DLNARenderer_play {
- my ($hash) = @_;
-
- #start play
- if($hash->{helper}{caskeid}) {
- if($hash->{READINGS}{sessionId}{VAL} eq "") {
- DLNARenderer_createSession($hash);
- }
- DLNARenderer_upnpSyncPlay($hash);
- } else {
- DLNARenderer_upnpPlay($hash);
- }
-
- return undef;
- }
- ###########################
- ##### CASKEID #############
- ###########################
- # BTCaskeid
- sub DLNARenderer_enableBTCaskeid {
- my ($hash) = @_;
- DLNARenderer_upnpAddToGroup($hash, "4DAA44C0-8291-11E3-BAA7-0800200C9A66", "Bluetooth");
- }
- sub DLNARenderer_disableBTCaskeid {
- my ($hash) = @_;
- DLNARenderer_upnpRemoveFromGroup($hash, "4DAA44C0-8291-11E3-BAA7-0800200C9A66");
- }
- # Stereo Mode
- sub DLNARenderer_setStereoMode {
- my ($hash, $leftSpeaker, $rightSpeaker, $name) = @_;
-
- DLNARenderer_destroyCurrentSession($hash);
- my @multiRoomDevices = DLNARenderer_getAllDLNARenderersWithCaskeid($hash);
- my $uuid = DLNARenderer_createUuid($hash);
-
- foreach my $device (@multiRoomDevices) {
- if(ReadingsVal($device->{NAME}, "friendlyName", "") eq $leftSpeaker) {
- DLNARenderer_setMultiChannelSpeaker($device, "left", $uuid, $leftSpeaker);
- readingsSingleUpdate($hash, "stereoLeft", $leftSpeaker, 1);
- } elsif(ReadingsVal($device->{NAME}, "friendlyName", "") eq $rightSpeaker) {
- DLNARenderer_setMultiChannelSpeaker($device, "right", $uuid, $rightSpeaker);
- readingsSingleUpdate($hash, "stereoRight", $rightSpeaker, 1);
- }
- }
- }
- sub DLNARenderer_updateStereoMode {
- my ($hash) = @_;
-
- if(!defined($hash->{helper}{device})) {
- InternalTimer(gettimeofday() + 10, 'DLNARenderer_updateStereoMode', $hash, 0);
- return undef;
- }
-
- if($hash->{helper}{caskeid} == 0) {
- return undef;
- }
-
- my $result = DLNARenderer_upnpGetMultiChannelSpeaker($hash);
- if($result) {
- InternalTimer(gettimeofday() + 300, 'DLNARenderer_updateStereoMode', $hash, 0);
- DLNARenderer_readingsSingleUpdateIfChanged($hash, "stereoSupport", 1, 1);
- } else {
- #speaker does not support multi channel
- DLNARenderer_readingsSingleUpdateIfChanged($hash, "stereoSupport", 0, 1);
- return undef;
- }
-
- my $mcsType = $result->getValue("CurrentMCSType");
- my $mcsId = $result->getValue("CurrentMCSID");
- my $mcsFriendlyName = $result->getValue("CurrentMCSFriendlyName");
- my $mcsSpeakerChannel = $result->getValue("CurrentSpeakerChannel");
-
- DLNARenderer_readingsSingleUpdateIfChanged($hash, "stereoPairName", $mcsFriendlyName, 1);
- DLNARenderer_readingsSingleUpdateIfChanged($hash, "stereoId", $mcsId, 1);
-
- if($mcsId eq "") {
- DLNARenderer_readingsSingleUpdateIfChanged($hash, "stereoLeft", "", 1);
- DLNARenderer_readingsSingleUpdateIfChanged($hash, "stereoRight", "", 1);
- } else {
- #THIS speaker is the left or right speaker
- DLNARenderer_setStereoSpeakerReading($hash, $hash, $mcsType, $mcsId, $mcsFriendlyName, $mcsSpeakerChannel);
- #set left/right speaker for OTHER speaker if OTHER speaker has same mcsId
- my @allHashes = DLNARenderer_getAllDLNARenderersWithCaskeid($hash);
- foreach my $hash2 (@allHashes) {
- my $result2 = DLNARenderer_upnpGetMultiChannelSpeaker($hash2);
- next if(!defined($result2));
-
- my $mcsType2 = $result2->getValue("CurrentMCSType");
- my $mcsId2 = $result2->getValue("CurrentMCSID");
- my $mcsFriendlyName2 = $result2->getValue("CurrentMCSFriendlyName");
- my $mcsSpeakerChannel2 = $result2->getValue("CurrentSpeakerChannel");
-
- if($mcsId2 eq $mcsId) {
- DLNARenderer_setStereoSpeakerReading($hash, $hash2, $mcsType2, $mcsId2, $mcsFriendlyName2, $mcsSpeakerChannel2);
- }
- }
- }
- }
- sub DLNARenderer_setStereoSpeakerReading {
- my ($hash, $speakerHash, $mcsType, $mcsId, $mcsFriendlyName, $mcsSpeakerChannel) = @_;
- DLNARenderer_readingsSingleUpdateIfChanged($hash, "stereoId", $mcsId, 1);
- if($mcsSpeakerChannel eq "LEFT_FRONT") {
- DLNARenderer_readingsSingleUpdateIfChanged($hash, "stereoLeft", ReadingsVal($speakerHash->{NAME}, "friendlyName", ""), 1);
- } elsif($mcsSpeakerChannel eq "RIGHT_FRONT") {
- DLNARenderer_readingsSingleUpdateIfChanged($hash, "stereoRight", ReadingsVal($speakerHash->{NAME}, "friendlyName", ""), 1);
- }
- }
- sub DLNARenderer_readingsSingleUpdateIfChanged {
- my ($hash, $reading, $value, $trigger) = @_;
- my $curVal = ReadingsVal($hash->{NAME}, $reading, "");
-
- if($curVal ne $value) {
- readingsSingleUpdate($hash, $reading, $value, $trigger);
- }
- }
- sub DLNARenderer_setMultiChannelSpeaker {
- my ($hash, $mode, $uuid, $name) = @_;
- my $uuidStr;
-
- if($mode eq "standalone") {
- DLNARenderer_upnpSetMultiChannelSpeaker($hash, "STANDALONE", "", "", "STANDALONE_SPEAKER");
- } elsif($mode eq "left") {
- DLNARenderer_upnpSetMultiChannelSpeaker($hash, "STEREO", $uuid, $name, "LEFT_FRONT");
- } elsif($mode eq "right") {
- DLNARenderer_upnpSetMultiChannelSpeaker($hash, "STEREO", $uuid, $name, "RIGHT_FRONT");
- }
-
- return undef;
- }
- sub DLNARenderer_setStandaloneMode {
- my ($hash) = @_;
- my @multiRoomDevices = DLNARenderer_getAllDLNARenderersWithCaskeid($hash);
- my $rightSpeaker = ReadingsVal($hash->{NAME}, "stereoRight", "");
- my $leftSpeaker = ReadingsVal($hash->{NAME}, "stereoLeft", "");
-
- foreach my $device (@multiRoomDevices) {
- if(ReadingsVal($device->{NAME}, "friendlyName", "") eq $leftSpeaker or
- ReadingsVal($device->{NAME}, "friendlyName", "") eq $rightSpeaker) {
- DLNARenderer_setMultiChannelSpeaker($device, "standalone", "", "");
- }
- }
-
- readingsSingleUpdate($hash, "stereoLeft", "", 1);
- readingsSingleUpdate($hash, "stereoRight", "", 1);
- readingsSingleUpdate($hash, "stereoId", "", 1);
-
- return undef;
- }
- sub DLNARenderer_createUuid {
- my ($hash) = @_;
- my $ug = Data::UUID->new();
- my $uuid = $ug->create();
- my $uuidStr = $ug->to_string($uuid);
-
- return $uuidStr;
- }
- # SessionManagement
- sub DLNARenderer_createSession {
- my ($hash) = @_;
- return DLNARenderer_upnpCreateSession($hash, "FHEM_Session");
- }
- sub DLNARenderer_getSession {
- my ($hash) = @_;
- return DLNARenderer_upnpGetSession($hash);
- }
- sub DLNARenderer_destroySession {
- my ($hash, $session) = @_;
-
- return DLNARenderer_upnpDestroySession($hash, $session);
- }
- sub DLNARenderer_destroyCurrentSession {
- my ($hash) = @_;
-
- my $result = DLNARenderer_getSession($hash);
- if($result->getValue("SessionID") ne "") {
- DLNARenderer_destroySession($hash, $result->getValue("SessionID"));
- }
- }
- sub DLNARenderer_addUnitToPlay {
- my ($hash, $unit) = @_;
-
- my $session = DLNARenderer_getSession($hash)->getValue("SessionID");
-
- if($session eq "") {
- $session = DLNARenderer_createSession($hash)->getValue("SessionID");
- }
-
- DLNARenderer_addUnitToSession($hash, $unit, $session);
- }
- sub DLNARenderer_removeUnitToPlay {
- my ($hash, $unit) = @_;
-
- my $session = DLNARenderer_getSession($hash)->getValue("SessionID");
-
- if($session ne "") {
- DLNARenderer_removeUnitFromSession($hash, $unit, $session);
- }
- }
- sub DLNARenderer_addUnitToSession {
- my ($hash, $uuid, $session) = @_;
-
- return DLNARenderer_upnpAddUnitToSession($hash, $session, $uuid);
- }
- sub DLNARenderer_removeUnitFromSession {
- my ($hash, $uuid, $session) = @_;
-
- return DLNARenderer_upnpRemoveUnitFromSession($hash, $session, $uuid);
- }
- # Group Definitions
- sub DLNARenderer_getGroupDefinition {
- #used for ... play Bad ...
- my ($hash, $groupName) = @_;
- my $currentGroupSettings = AttrVal($hash->{NAME}, "multiRoomGroups", "");
-
- #regex Bad[MUNET1,MUNET2],WZ[L:MUNET2,R:MUNET3],...
- while ($currentGroupSettings =~ /([a-zA-Z0-9äöüßÄÜÖ_]+)\[([a-zA-Z,0-9:äöüßÄÜÖ_]+)/g) {
- my $group = $1;
- my $groupMembers = $2;
-
- Log3 $hash, 4, "DLNARenderer: Groupdefinition $group => $groupMembers";
-
- if($group eq $groupName) {
- return $groupMembers;
- }
- }
-
- return undef;
- }
- sub DLNARenderer_saveGroupAs {
- my ($hash, $groupName) = @_;
- my $currentGroupSettings = AttrVal($hash->{NAME}, "multiRoomGroups", "");
- $currentGroupSettings .= "," if($currentGroupSettings ne "");
-
- #session details
- my $currentSession = ReadingsVal($hash->{NAME}, "multiRoomUnits", "");
- #stereo mode
- my $stereoLeft = ReadingsVal($hash->{NAME}, "stereoLeft", "");
- my $stereoRight = ReadingsVal($hash->{NAME}, "stereoRight", "");
- my $stereoDevices = "L:$stereoLeft,R:$stereoRight" if($stereoLeft ne "" && $stereoRight ne "");
-
- return undef if($currentSession eq "" && $stereoLeft eq "" && $stereoRight eq "");
- $stereoDevices .= "," if($currentSession ne "" && $stereoDevices ne "");
-
- my $groupDefinition = $currentGroupSettings.$groupName."[".$stereoDevices.$currentSession."]";
-
- #save current session as group
- CommandAttr(undef, "$hash->{NAME} multiRoomGroups $groupDefinition");
-
- return undef;
- }
- sub DLNARenderer_addUnit {
- my ($hash, $unitName) = @_;
-
- my @caskeidClients = DLNARenderer_getAllDLNARenderersWithCaskeid($hash);
- foreach my $client (@caskeidClients) {
- if(ReadingsVal($client->{NAME}, "friendlyName", "") eq $unitName) {
- my @multiRoomUnits = split(",", ReadingsVal($hash->{NAME}, "multiRoomUnits", ""));
- foreach my $unit (@multiRoomUnits) {
- #skip if unit is already part of the session
- return undef if($unit eq $unitName);
- }
- #add unit to session
- DLNARenderer_addUnitToPlay($hash, substr($client->{UDN},5));
- return undef;
- }
- }
- return "DLNARenderer: No unit $unitName found.";
- }
- ##############################
- ####### UPNP FUNCTIONS #######
- ##############################
- sub DLNARenderer_upnpPause {
- my ($hash) = @_;
- return DLNARenderer_upnpCallAVTransport($hash, "Pause", 0);
- }
- sub DLNARenderer_upnpSetAVTransportURI {
- my ($hash, $stream, $meta) = @_;
- if (!defined($meta)) { $meta = ""; }
- return DLNARenderer_upnpCallAVTransport($hash, "SetAVTransportURI", 0, $stream, $meta);
- }
- sub DLNARenderer_upnpStop {
- my ($hash) = @_;
- return DLNARenderer_upnpCallAVTransport($hash, "Stop", 0);
- }
- sub DLNARenderer_upnpSeek {
- my ($hash, $seekTime) = @_;
- return DLNARenderer_upnpCallAVTransport($hash, "Seek", 0, "REL_TIME", $seekTime);
- }
- sub DLNARenderer_upnpNext {
- my ($hash) = @_;
- return DLNARenderer_upnpCallAVTransport($hash, "Next", 0);
- }
- sub DLNARenderer_upnpPrevious {
- my ($hash) = @_;
- return DLNARenderer_upnpCallAVTransport($hash, "Previous", 0);
- }
- sub DLNARenderer_upnpPlay {
- my ($hash) = @_;
- return DLNARenderer_upnpCallAVTransport($hash, "Play", 0, 1);
- }
- sub DLNARenderer_upnpSyncPlay {
- my ($hash) = @_;
- return DLNARenderer_upnpCallAVTransport($hash, "SyncPlay", 0, 1, "REL_TIME", "", "", "", "PUREDEVICECLOCK1");
- }
- sub DLNARenderer_upnpCallAVTransport {
- my ($hash, $method, @args) = @_;
- return DLNARenderer_upnpCall($hash, 'AVTransport', $method, @args);
- }
- sub DLNARenderer_upnpGetMultiChannelSpeaker {
- my ($hash) = @_;
- return DLNARenderer_upnpCallSpeakerManagement($hash, "GetMultiChannelSpeaker");
- }
- sub DLNARenderer_upnpSetMultiChannelSpeaker {
- my ($hash, @args) = @_;
- return DLNARenderer_upnpCallSpeakerManagement($hash, "SetMultiChannelSpeaker", @args);
- }
- sub DLNARenderer_upnpCallSpeakerManagement {
- my ($hash, $method, @args) = @_;
- return DLNARenderer_upnpCall($hash, 'SpeakerManagement', $method, @args);
- }
- sub DLNARenderer_upnpAddUnitToSession {
- my ($hash, $session, $uuid) = @_;
- return DLNARenderer_upnpCallSessionManagement($hash, "AddUnitToSession", $session, $uuid);
- }
- sub DLNARenderer_upnpRemoveUnitFromSession {
- my ($hash, $session, $uuid) = @_;
- return DLNARenderer_upnpCallSessionManagement($hash, "RemoveUnitFromSession", $session, $uuid);
- }
- sub DLNARenderer_upnpDestroySession {
- my ($hash, $session) = @_;
- return DLNARenderer_upnpCallSessionManagement($hash, "DestroySession", $session);
- }
- sub DLNARenderer_upnpCreateSession {
- my ($hash, $name) = @_;
- return DLNARenderer_upnpCallSessionManagement($hash, "CreateSession", $name);
- }
- sub DLNARenderer_upnpGetSession {
- my ($hash) = @_;
- return DLNARenderer_upnpCallSessionManagement($hash, "GetSession");
- }
- sub DLNARenderer_upnpAddToGroup {
- my ($hash, $unit, $name) = @_;
- return DLNARenderer_upnpCallSpeakerManagement($hash, "AddToGroup", $unit, $name, "");
- }
- sub DLNARenderer_upnpRemoveFromGroup {
- my ($hash, $unit) = @_;
- return DLNARenderer_upnpCallSpeakerManagement($hash, "RemoveFromGroup", $unit);
- }
- sub DLNARenderer_upnpCallSessionManagement {
- my ($hash, $method, @args) = @_;
- return DLNARenderer_upnpCall($hash, 'SessionManagement', $method, @args);
- }
- sub DLNARenderer_upnpSetVolume {
- my ($hash, $targetVolume) = @_;
- return DLNARenderer_upnpCallRenderingControl($hash, "SetVolume", 0, "Master", $targetVolume);
- }
- sub DLNARenderer_upnpSetMute {
- my ($hash, $muteState) = @_;
- return DLNARenderer_upnpCallRenderingControl($hash, "SetMute", 0, "Master", $muteState);
- }
- sub DLNARenderer_upnpCallRenderingControl {
- my ($hash, $method, @args) = @_;
- return DLNARenderer_upnpCall($hash, 'RenderingControl', $method, @args);
- }
- sub DLNARenderer_upnpCall {
- my ($hash, $service, $method, @args) = @_;
- my $upnpService = DLNARenderer_upnpGetService($hash, $service);
- my $ret = undef;
- my $i = 0;
-
- do {
- eval {
- my $upnpServiceCtrlProxy = $upnpService->controlProxy();
- my $methodExists = $upnpService->getAction($method);
- if($methodExists) {
- $ret = $upnpServiceCtrlProxy->$method(@args);
- Log3 $hash, 5, "DLNARenderer: $service, $method(".join(",",@args).") succeed.";
- } else {
- Log3 $hash, 4, "DLNARenderer: $service, $method(".join(",",@args).") does not exist.";
- }
- };
- if($@) {
- Log3 $hash, 3, "DLNARenderer: $service, $method(".join(",",@args).") failed, $@";
- }
- $i = $i+1;
- } while(!defined($ret) && $i < 3);
-
- return $ret;
- }
- sub DLNARenderer_upnpGetService {
- my ($hash, $service) = @_;
- my $upnpService;
-
- foreach my $srvc ($hash->{helper}{device}->services) {
- my @srvcParts = split(":", $srvc->serviceType);
- my $serviceName = $srvcParts[-2];
- if($serviceName eq $service) {
- Log3 $hash, 5, "DLNARenderer: $service: ".$srvc->serviceType." found. OK.";
- $upnpService = $srvc;
- }
- }
-
- if(!defined($upnpService)) {
- Log3 $hash, 4, "DLNARenderer: $service unknown for $hash->{NAME}.";
- return undef;
- }
-
- return $upnpService;
- }
-
- ##############################
- ####### EVENT HANDLING #######
- ##############################
- sub DLNARenderer_processEventXml {
- my ($hash, $property, $xml) = @_;
- Log3 $hash, 4, "DLNARenderer: ".Dumper($xml);
-
- if($property eq "LastChange") {
- return undef if($xml eq "");
-
- if($xml->{Event}) {
- if (index($xml->{Event}{xmlns},"urn:schemas-upnp-org:metadata-1-0/AVT")==0) {
- #process AV Transport
- my $e = $xml->{Event}{InstanceID};
- #DLNARenderer_updateReadingByEvent($hash, "NumberOfTracks", $e->{NumberOfTracks});
- DLNARenderer_updateReadingByEvent($hash, "transportState", $e->{TransportState});
- DLNARenderer_updateReadingByEvent($hash, "transportStatus", $e->{TransportStatus});
- #DLNARenderer_updateReadingByEvent($hash, "TransportPlaySpeed", $e->{TransportPlaySpeed});
- #DLNARenderer_updateReadingByEvent($hash, "PlaybackStorageMedium", $e->{PlaybackStorageMedium});
- #DLNARenderer_updateReadingByEvent($hash, "RecordStorageMedium", $e->{RecordStorageMedium});
- #DLNARenderer_updateReadingByEvent($hash, "RecordMediumWriteStatus", $e->{RecordMediumWriteStatus});
- #DLNARenderer_updateReadingByEvent($hash, "CurrentRecordQualityMode", $e->{CurrentRecordQualityMode});
- #DLNARenderer_updateReadingByEvent($hash, "PossibleRecordQualityMode", $e->{PossibleRecordQualityMode});
- DLNARenderer_updateReadingByEvent($hash, "currentTrackURI", $e->{CurrentTrackURI});
- #DLNARenderer_updateReadingByEvent($hash, "AVTransportURI", $e->{AVTransportURI});
- DLNARenderer_updateReadingByEvent($hash, "nextAVTransportURI", $e->{NextAVTransportURI});
- #DLNARenderer_updateReadingByEvent($hash, "RelativeTimePosition", $e->{RelativeTimePosition});
- #DLNARenderer_updateReadingByEvent($hash, "AbsoluteTimePosition", $e->{AbsoluteTimePosition});
- #DLNARenderer_updateReadingByEvent($hash, "RelativeCounterPosition", $e->{RelativeCounterPosition});
- #DLNARenderer_updateReadingByEvent($hash, "AbsoluteCounterPosition", $e->{AbsoluteCounterPosition});
- #DLNARenderer_updateReadingByEvent($hash, "CurrentTrack", $e->{CurrentTrack});
- #DLNARenderer_updateReadingByEvent($hash, "CurrentMediaDuration", $e->{CurrentMediaDuration});
- #DLNARenderer_updateReadingByEvent($hash, "CurrentTrackDuration", $e->{CurrentTrackDuration});
- #DLNARenderer_updateReadingByEvent($hash, "CurrentPlayMode", $e->{CurrentPlayMode});
- #handle metadata
- #DLNARenderer_updateReadingByEvent($hash, "AVTransportURIMetaData", $e->{AVTransportURIMetaData});
- #DLNARenderer_updateMetaData($hash, "current", $e->{AVTransportURIMetaData});
- #DLNARenderer_updateReadingByEvent($hash, "NextAVTransportURIMetaData", $e->{NextAVTransportURIMetaData});
- DLNARenderer_updateMetaData($hash, "next", $e->{NextAVTransportURIMetaData});
- #use only CurrentTrackMetaData instead of AVTransportURIMetaData
- #DLNARenderer_updateReadingByEvent($hash, "CurrentTrackMetaData", $e->{CurrentTrackMetaData});
- DLNARenderer_updateMetaData($hash, "current", $e->{CurrentTrackMetaData});
-
- #update state
- my $transportState = ReadingsVal($hash->{NAME}, "transportState", "");
- if(ReadingsVal($hash->{NAME}, "presence", "") ne "offline") {
- if($transportState eq "PAUSED_PLAYBACK") {
- readingsSingleUpdate($hash, "state", "paused", 1);
- } elsif($transportState eq "PLAYING") {
- readingsSingleUpdate($hash, "state", "playing", 1);
- } elsif($transportState eq "TRANSITIONING") {
- readingsSingleUpdate($hash, "state", "buffering", 1);
- } elsif($transportState eq "STOPPED") {
- readingsSingleUpdate($hash, "state", "stopped", 1);
- } elsif($transportState eq "NO_MEDIA_PRESENT") {
- readingsSingleUpdate($hash, "state", "online", 1);
- }
- }
- } elsif (index($xml->{Event}{xmlns},"urn:schemas-upnp-org:metadata-1-0/RCS")==0) {
- #process RenderingControl
- my $e = $xml->{Event}{InstanceID};
- DLNARenderer_updateVolumeByEvent($hash, "mute", $e->{Mute});
- DLNARenderer_updateVolumeByEvent($hash, "volume", $e->{Volume});
- readingsSingleUpdate($hash, "multiRoomVolume", ReadingsVal($hash->{NAME}, "volume", 0), 1);
- } elsif ($xml->{Event}{xmlns} eq "FIXME SpeakerManagement") {
- #process SpeakerManagement
- }
- }
- } elsif($property eq "Groups") {
- #handle BTCaskeid
- my $btCaskeidState = 0;
- foreach my $group (@{$xml->{groups}{group}}) {
- #"4DAA44C0-8291-11E3-BAA7-0800200C9A66", "Bluetooth"
- if($group->{id} eq "4DAA44C0-8291-11E3-BAA7-0800200C9A66") {
- $btCaskeidState = 1;
- }
- }
- #TODO update only if changed
- readingsSingleUpdate($hash, "btCaskeid", $btCaskeidState, 1);
- } elsif($property eq "SessionID") {
- #TODO search for other speakers with same sessionId and add them to multiRoomUnits
- readingsSingleUpdate($hash, "sessionId", $xml, 1);
- }
-
- return undef;
- }
- sub DLNARenderer_updateReadingByEvent {
- my ($hash, $readingName, $xmlEvent) = @_;
-
- my $currVal = ReadingsVal($hash->{NAME}, $readingName, "");
-
- if($xmlEvent) {
- Log3 $hash, 4, "DLNARenderer: Update reading $readingName with ".$xmlEvent->{val};
- my $val = $xmlEvent->{val};
- $val = "" if(ref $val eq ref {});
- if($val ne $currVal) {
- readingsSingleUpdate($hash, $readingName, $val, 1);
- }
- }
-
- return undef;
- }
- sub DLNARenderer_updateVolumeByEvent {
- my ($hash, $readingName, $volume) = @_;
- my $balance = 0;
- my $balanceSupport = 0;
-
- foreach my $vol (@{$volume}) {
- my $channel = $vol->{Channel} ? $vol->{Channel} : $vol->{channel};
- if($channel) {
- if($channel eq "Master") {
- DLNARenderer_updateReadingByEvent($hash, $readingName, $vol);
- } elsif($channel eq "LF") {
- $balance -= $vol->{val};
- $balanceSupport = 1;
- } elsif($channel eq "RF") {
- $balance += $vol->{val};
- $balanceSupport = 1;
- }
- } else {
- DLNARenderer_updateReadingByEvent($hash, $readingName, $vol);
- }
- }
-
- if($readingName eq "volume" && $balanceSupport == 1) {
- readingsSingleUpdate($hash, "balance", $balance, 1);
- }
-
- return undef;
- }
- sub DLNARenderer_updateMetaData {
- my ($hash, $prefix, $metaData) = @_;
- my $metaDataAvailable = 0;
- $metaDataAvailable = 1 if(defined($metaData) && $metaData->{val} && $metaData->{val} ne "");
-
- if($metaDataAvailable) {
- my $xml;
- if($metaData->{val} eq "NOT_IMPLEMENTED") {
- readingsSingleUpdate($hash, $prefix."Title", "", 1);
- readingsSingleUpdate($hash, $prefix."Artist", "", 1);
- readingsSingleUpdate($hash, $prefix."Album", "", 1);
- readingsSingleUpdate($hash, $prefix."AlbumArtist", "", 1);
- readingsSingleUpdate($hash, $prefix."AlbumArtURI", "", 1);
- readingsSingleUpdate($hash, $prefix."OriginalTrackNumber", "", 1);
- readingsSingleUpdate($hash, $prefix."Duration", "", 1);
- } else {
- eval {
- $xml = XMLin($metaData->{val}, KeepRoot => 1, ForceArray => [], KeyAttr => []);
- Log3 $hash, 4, "DLNARenderer: MetaData: ".Dumper($xml);
- };
- if(!$@) {
- DLNARenderer_updateMetaDataItemPart($hash, $prefix."Title", $xml->{"DIDL-Lite"}{item}{"dc:title"});
- DLNARenderer_updateMetaDataItemPart($hash, $prefix."Artist", $xml->{"DIDL-Lite"}{item}{"dc:creator"});
- DLNARenderer_updateMetaDataItemPart($hash, $prefix."Album", $xml->{"DIDL-Lite"}{item}{"upnp:album"});
- DLNARenderer_updateMetaDataItemPart($hash, $prefix."AlbumArtist", $xml->{"DIDL-Lite"}{item}{"r:albumArtist"});
- if($xml->{"DIDL-Lite"}{item}{"upnp:albumArtURI"}) {
- DLNARenderer_updateMetaDataItemPart($hash, $prefix."AlbumArtURI", $xml->{"DIDL-Lite"}{item}{"upnp:albumArtURI"});
- } else {
- readingsSingleUpdate($hash, $prefix."AlbumArtURI", "", 1);
- }
- DLNARenderer_updateMetaDataItemPart($hash, $prefix."OriginalTrackNumber", $xml->{"DIDL-Lite"}{item}{"upnp:originalTrackNumber"});
- if($xml->{"DIDL-Lite"}{item}{res}) {
- DLNARenderer_updateMetaDataItemPart($hash, $prefix."Duration", $xml->{"DIDL-Lite"}{item}{res}{duration});
- } else {
- readingsSingleUpdate($hash, $prefix."Duration", "", 1);
- }
- } else {
- Log3 $hash, 1, "DLNARenderer: XML parsing error: ".$@;
- }
- }
- }
- return undef;
- }
- sub DLNARenderer_updateMetaDataItemPart {
- my ($hash, $readingName, $item) = @_;
- my $currVal = ReadingsVal($hash->{NAME}, $readingName, "");
- if($item) {
- $item = "" if(ref $item eq ref {});
- if($currVal ne $item) {
- readingsSingleUpdate($hash, $readingName, $item, 1);
- }
- }
-
- return undef;
- }
- ##############################
- ####### DISCOVERY ############
- ##############################
- sub DLNARenderer_setupControlpoint {
- my ($hash) = @_;
- my $error;
- my $cp;
- my @usedonlyIPs = split(/,/, AttrVal($hash->{NAME}, 'usedonlyIPs', ''));
- my @ignoredIPs = split(/,/, AttrVal($hash->{NAME}, 'ignoredIPs', ''));
-
- do {
- eval {
- $cp = UPnP::ControlPoint->new(SearchPort => 0, SubscriptionPort => 0, MaxWait => 30, UsedOnlyIP => \@usedonlyIPs, IgnoreIP => \@ignoredIPs, LogLevel => AttrVal($hash->{NAME}, 'verbose', 0));
- $hash->{helper}{controlpoint} = $cp;
-
- DLNARenderer_addSocketsToMainloop($hash);
- };
- $error = $@;
- } while($error);
-
- return undef;
- }
- sub DLNARenderer_startDlnaRendererSearch {
- my ($hash) = @_;
- eval {
- $hash->{helper}{controlpoint}->searchByType('urn:schemas-upnp-org:device:MediaRenderer:1', sub { DLNARenderer_discoverCallback($hash, @_); });
- };
- if($@) {
- Log3 $hash, 2, "DLNARenderer: Search failed with error $@";
- }
- return undef;
- }
- sub DLNARenderer_discoverCallback {
- my ($hash, $search, $device, $action) = @_;
-
- Log3 $hash, 4, "DLNARenderer: $action, ".$device->friendlyName();
- if($action eq "deviceAdded") {
- DLNARenderer_addedDevice($hash, $device);
- } elsif($action eq "deviceRemoved") {
- DLNARenderer_removedDevice($hash, $device);
- }
- return undef;
- }
- sub DLNARenderer_subscriptionCallback {
- my ($hash, $service, %properties) = @_;
-
- Log3 $hash, 4, "DLNARenderer: Received event: ".Dumper(%properties);
-
- foreach my $property (keys %properties) {
-
- $properties{$property} = decode_entities($properties{$property});
-
- my $xml;
- eval {
- if($properties{$property} =~ /xml/) {
- $xml = XMLin($properties{$property}, KeepRoot => 1, ForceArray => [qw(Volume Mute Loudness VolumeDB group)], KeyAttr => []);
- } else {
- $xml = $properties{$property};
- }
- };
-
- if($@) {
- Log3 $hash, 2, "DLNARenderer: XML formatting error: ".$@.", ".$properties{$property};
- next;
- }
-
- DLNARenderer_processEventXml($hash, $property, $xml);
- }
-
- return undef;
- }
- sub DLNARenderer_renewSubscriptions {
- my ($hash) = @_;
- my $dev = $hash->{helper}{device};
-
- InternalTimer(gettimeofday() + 200, 'DLNARenderer_renewSubscriptions', $hash, 0);
-
- return undef if(!defined($dev));
-
- BlockingCall('DLNARenderer_renewSubscriptionBlocking', $hash->{NAME});
-
- return undef;
- }
- sub DLNARenderer_renewSubscriptionBlocking {
- my ($string) = @_;
- my ($name) = split("\\|", $string);
- my $hash = $main::defs{$name};
- $SIG{__WARN__} = sub {
- my ($called_from) = caller(0);
- my $wrn_text = shift;
- Log3 $hash, 5, "DLNARenderer: ".$called_from.", ".$wrn_text;
- };
-
- #register callbacks
- #urn:upnp-org:serviceId:AVTransport
- eval {
- if(defined($hash->{helper}{avTransportSubscription})) {
- $hash->{helper}{avTransportSubscription}->renew();
- }
- };
-
- #urn:upnp-org:serviceId:RenderingControl
- eval {
- if(defined($hash->{helper}{renderingControlSubscription})) {
- $hash->{helper}{renderingControlSubscription}->renew();
- }
- };
-
- #urn:pure-com:serviceId:SpeakerManagement
- eval {
- if(defined($hash->{helper}{speakerManagementSubscription})) {
- $hash->{helper}{speakerManagementSubscription}->renew();
- }
- };
- }
- sub DLNARenderer_addedDevice {
- my ($hash, $dev) = @_;
-
- my $udn = $dev->UDN();
- #TODO check for BOSE UDN
- #ignoreUDNs
- return undef if(AttrVal($hash->{NAME}, "ignoreUDNs", "") =~ /$udn/);
- #acceptedUDNs
- my $acceptedUDNs = AttrVal($hash->{NAME}, "acceptedUDNs", "");
- return undef if($acceptedUDNs ne "" && $acceptedUDNs !~ /$udn/);
-
- my $foundDevice = 0;
- my @allDLNARenderers = DLNARenderer_getAllDLNARenderers($hash);
- foreach my $DLNARendererHash (@allDLNARenderers) {
- if($DLNARendererHash->{UDN} eq $dev->UDN()) {
- $foundDevice = 1;
- }
- }
- if(!$foundDevice) {
- my $uniqueDeviceName = "DLNA_".substr($dev->UDN(),29,12);
- if(length($uniqueDeviceName) < 17) {
- $uniqueDeviceName = "DLNA_".substr($dev->UDN(),5);
- $uniqueDeviceName =~ tr/-/_/;
- }
- CommandDefine(undef, "$uniqueDeviceName DLNARenderer ".$dev->UDN());
- CommandAttr(undef,"$uniqueDeviceName alias ".$dev->friendlyName());
- CommandAttr(undef,"$uniqueDeviceName webCmd volume");
- if(AttrVal($hash->{NAME}, "defaultRoom", "") ne "") {
- CommandAttr(undef,"$uniqueDeviceName room ".AttrVal($hash->{NAME}, "defaultRoom", ""));
- }
- Log3 $hash, 3, "DLNARenderer: Created device $uniqueDeviceName for ".$dev->friendlyName();
-
- #update list
- @allDLNARenderers = DLNARenderer_getAllDLNARenderers($hash);
- }
-
- foreach my $DLNARendererHash (@allDLNARenderers) {
- if($DLNARendererHash->{UDN} eq $dev->UDN()) {
- #device found, update data
- $DLNARendererHash->{helper}{device} = $dev;
-
- #update device information (FIXME only on change)
- readingsSingleUpdate($DLNARendererHash, "friendlyName", $dev->friendlyName(), 1);
- readingsSingleUpdate($DLNARendererHash, "manufacturer", $dev->manufacturer(), 1);
- readingsSingleUpdate($DLNARendererHash, "modelDescription", $dev->modelDescription(), 1);
- readingsSingleUpdate($DLNARendererHash, "modelName", $dev->modelName(), 1);
- readingsSingleUpdate($DLNARendererHash, "modelNumber", $dev->modelNumber(), 1);
- readingsSingleUpdate($DLNARendererHash, "modelURL", $dev->modelURL(), 1);
- readingsSingleUpdate($DLNARendererHash, "manufacturerURL", $dev->manufacturerURL(), 1);
- readingsSingleUpdate($DLNARendererHash, "presentationURL", $dev->presentationURL(), 1);
- readingsSingleUpdate($DLNARendererHash, "manufacturer", $dev->manufacturer(), 1);
-
- #register callbacks
- #urn:upnp-org:serviceId:AVTransport
- if(DLNARenderer_upnpGetService($DLNARendererHash, "AVTransport")) {
- $DLNARendererHash->{helper}{avTransportSubscription} = DLNARenderer_upnpGetService($DLNARendererHash, "AVTransport")->subscribe(sub { DLNARenderer_subscriptionCallback($DLNARendererHash, @_); }, 1);
- }
- #urn:upnp-org:serviceId:RenderingControl
- if(DLNARenderer_upnpGetService($DLNARendererHash, "RenderingControl")) {
- $DLNARendererHash->{helper}{renderingControlSubscription} = DLNARenderer_upnpGetService($DLNARendererHash, "RenderingControl")->subscribe(sub { DLNARenderer_subscriptionCallback($DLNARendererHash, @_); }, 1);
- }
- #urn:pure-com:serviceId:SpeakerManagement
- if(DLNARenderer_upnpGetService($DLNARendererHash, "SpeakerManagement")) {
- $DLNARendererHash->{helper}{speakerManagementSubscription} = DLNARenderer_upnpGetService($DLNARendererHash, "SpeakerManagement")->subscribe(sub { DLNARenderer_subscriptionCallback($DLNARendererHash, @_); }, 1);
- }
-
- #set online
- readingsSingleUpdate($DLNARendererHash,"presence","online",1);
- if(ReadingsVal($DLNARendererHash->{NAME}, "state", "") eq "offline") {
- readingsSingleUpdate($DLNARendererHash,"state","online",1);
- }
-
- #check caskeid
- if(DLNARenderer_upnpGetService($DLNARendererHash, "SessionManagement")) {
- $DLNARendererHash->{helper}{caskeid} = 1;
- readingsSingleUpdate($DLNARendererHash,"multiRoomSupport","1",1);
- } else {
- readingsSingleUpdate($DLNARendererHash,"multiRoomSupport","0",1);
- }
-
- #update list of caskeid clients
- my @caskeidClients = DLNARenderer_getAllDLNARenderersWithCaskeid($hash);
- $DLNARendererHash->{helper}{caskeidClients} = "";
- foreach my $client (@caskeidClients) {
- #do not add myself
- if($client->{UDN} ne $DLNARendererHash->{UDN}) {
- $DLNARendererHash->{helper}{caskeidClients} .= ",".ReadingsVal($client->{NAME}, "friendlyName", "");
- }
- }
- $DLNARendererHash->{helper}{caskeidClients} = substr($DLNARendererHash->{helper}{caskeidClients}, 1) if($DLNARendererHash->{helper}{caskeidClients} ne "");
- InternalTimer(gettimeofday() + 200, 'DLNARenderer_renewSubscriptions', $DLNARendererHash, 0);
- InternalTimer(gettimeofday() + 60, 'DLNARenderer_updateStereoMode', $DLNARendererHash, 0);
- }
- }
-
- return undef;
- }
- sub DLNARenderer_removedDevice($$) {
- my ($hash, $device) = @_;
- my $deviceHash = DLNARenderer_getHashByUDN($hash, $device->UDN());
- return undef if(!defined($deviceHash));
-
- readingsSingleUpdate($deviceHash, "presence", "offline", 1);
- readingsSingleUpdate($deviceHash, "state", "offline", 1);
- RemoveInternalTimer($deviceHash, 'DLNARenderer_renewSubscriptions');
- RemoveInternalTimer($deviceHash, 'DLNARenderer_updateStereoMode');
- }
- ###############################
- ##### GET PLAYER FUNCTIONS ####
- ###############################
- sub DLNARenderer_getMainDLNARenderer($) {
- my ($hash) = @_;
-
- foreach my $fhem_dev (sort keys %main::defs) {
- return $main::defs{$fhem_dev} if($main::defs{$fhem_dev}{TYPE} eq 'DLNARenderer' && $main::defs{$fhem_dev}{UDN} eq "0");
- }
-
- return undef;
- }
- sub DLNARenderer_getHashByUDN($$) {
- my ($hash, $udn) = @_;
-
- foreach my $fhem_dev (sort keys %main::defs) {
- return $main::defs{$fhem_dev} if($main::defs{$fhem_dev}{TYPE} eq 'DLNARenderer' && $main::defs{$fhem_dev}{UDN} eq $udn);
- }
-
- return undef;
- }
- sub DLNARenderer_getHashByFriendlyName {
- my ($hash, $friendlyName) = @_;
-
- foreach my $fhem_dev (sort keys %main::defs) {
- my $devHash = $main::defs{$fhem_dev};
- return $devHash if($devHash->{TYPE} eq 'DLNARenderer' && ReadingsVal($devHash->{NAME}, "friendlyName", "") eq $friendlyName);
- }
-
- return undef;
- }
- sub DLNARenderer_getAllDLNARenderers($) {
- my ($hash) = @_;
- my @DLNARenderers = ();
-
- foreach my $fhem_dev (sort keys %main::defs) {
- push @DLNARenderers, $main::defs{$fhem_dev} if($main::defs{$fhem_dev}{TYPE} eq 'DLNARenderer' && $main::defs{$fhem_dev}{UDN} ne "0" && $main::defs{$fhem_dev}{UDN} ne "-1");
- }
-
- return @DLNARenderers;
- }
- sub DLNARenderer_getAllDLNARenderersWithCaskeid($) {
- my ($hash) = @_;
- my @caskeidClients = ();
-
- my @DLNARenderers = DLNARenderer_getAllDLNARenderers($hash);
- foreach my $DLNARenderer (@DLNARenderers) {
- push @caskeidClients, $DLNARenderer if($DLNARenderer->{helper}{caskeid});
- }
-
- return @caskeidClients;
- }
- ###############################
- ###### UTILITY FUNCTIONS ######
- ###############################
- sub DLNARenderer_generateDidlLiteAndPlay {
- my ($hash, $stream) = @_;
- BlockingCall('DLNARenderer_generateDidlLiteBlocking', $hash->{NAME}."|".$stream, 'DLNARenderer_generateDidlLiteBlockingFinished');
- return undef;
- }
- sub DLNARenderer_generateDidlLiteBlockingFinished {
- my ($string) = @_;
-
- return unless (defined($string));
-
- my ($name, $stream, $meta) = split("\\|",$string);
- my $hash = $defs{$name};
-
- DLNARenderer_upnpSetAVTransportURI($hash, $stream, $meta);
- DLNARenderer_play($hash);
- readingsSingleUpdate($hash, "stream", $stream, 1);
- }
- sub DLNARenderer_generateDidlLiteBlocking {
- my ($string) = @_;
- my ($name, $stream) = split("\\|", $string);
- my $hash = $main::defs{$name};
- my $ret = $name."|".$stream;
-
- if(index($stream, "http:") != 0) {
- return $ret;
- }
-
- my $ua = new LWP::UserAgent(agent => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1');
- $ua->max_size(0);
- my $resp = $ua->get($stream);
- my $didl_header = '<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/" xmlns:sec="http://www.sec.co.kr/"><item id="-1" parentID="parent" restricted="1">';
- my $didl_footer = '</item></DIDL-Lite>';
- $stream = encode_entities($stream);
- my $size = "";
- my $protocolInfo = "";
- my $album = $stream;
- my $title = $stream;
- my $meta = "";
- if (defined($resp->header('content-length'))) {
- $size = ' size="'.$resp->header('content-length').'"';
- }
- my @header = split /;/, $resp->header('content-type');
- my $contenttype = $header[0];
- if (defined($resp->header('contentfeatures.dlna.org'))) {
- $protocolInfo = "http-get:*:".$contenttype.":".$resp->header('contentfeatures.dlna.org');
- } else {
- $protocolInfo = "http-get:*:".$contenttype.":DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000";
- }
- if (defined($resp->header('icy-name'))) {
- $album = encode_entities($resp->header('icy-name'));
- }
- if (defined($resp->header('icy-genre'))) {
- $title = encode_entities($resp->header('icy-genre'));
- }
- if (substr($contenttype,0,5) eq "audio" or $contenttype eq "application/ogg") {
- $meta = $didl_header.'<upnp:class>object.item.audioItem.musicTrack</upnp:class><dc:title>'.$title.'</dc:title><upnp:album>'.$album.'</upnp:album><res protocolInfo="'.$protocolInfo.'"'.$size.'>'.$stream.'</res>'.$didl_footer;
- } elsif (substr($contenttype,0,5) eq "video") {
- $meta = $didl_header.'<upnp:class>object.item.videoItem</upnp:class><dc:title>'.$title.'</dc:title><upnp:album>'.$album.'</upnp:album><res protocolInfo="'.$protocolInfo.'"'.$size.'>'.$stream.'</res>'.$didl_footer;
- } else {
- $meta = "";
- }
- $ret .= "|".$meta;
- return $ret;
- }
- sub DLNARenderer_newChash($$$) {
- my ($hash,$socket,$chash) = @_;
- $chash->{TYPE} = $hash->{TYPE};
- $chash->{UDN} = -1;
- $chash->{NR} = $devcount++;
- $chash->{phash} = $hash;
- $chash->{PNAME} = $hash->{NAME};
- $chash->{CD} = $socket;
- $chash->{FD} = $socket->fileno();
- $chash->{PORT} = $socket->sockport if( $socket->sockport );
- $chash->{TEMPORARY} = 1;
- $attr{$chash->{NAME}}{room} = 'hidden';
- $defs{$chash->{NAME}} = $chash;
- $selectlist{$chash->{NAME}} = $chash;
- }
- sub DLNARenderer_closeSocket($) {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- RemoveInternalTimer($hash);
- close($hash->{CD});
- delete($hash->{CD});
- delete($selectlist{$name});
- delete($hash->{FD});
- }
- sub DLNARenderer_addSocketsToMainloop {
- my ($hash) = @_;
-
- my @sockets = $hash->{helper}{controlpoint}->sockets();
-
- #check if new sockets need to be added to mainloop
- foreach my $s (@sockets) {
- #create chash and add to selectlist
- my $chash = DLNARenderer_newChash($hash, $s, {NAME => "DLNASocket-".$hash->{NAME}."-".$s->fileno()});
- }
-
- return undef;
- }
- 1;
- =pod
- =item device
- =item summary Autodiscover and control your DLNA renderer devices easily
- =item summary_DE Autodiscover und einfache Steuerung deiner DLNA Renderer Geräte
- =begin html
- <a name="DLNARenderer"></a>
- <h3>DLNARenderer</h3>
- <ul>
- DLNARenderer automatically discovers all your MediaRenderer devices in your local network and allows you to fully control them.<br>
- It also supports multiroom audio for Caskeid and Bluetooth Caskeid speakers (e.g. MUNET).<br><br>
- <b>Note:</b> The followig libraries are required for this module:
- <ul><li>SOAP::Lite</li> <li>LWP::Simple</li> <li>XML::Simple</li> <li>XML::Parser::Lite</li> <li>LWP::UserAgent</li><br>
- </ul>
- <a name="DLNARendererdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> DLNARenderer</code>
- <br><br>
- Example:
- <ul>
- <code>define dlnadevices DLNARenderer</code><br>
- After about 2 minutes you can find all automatically created DLNA devices under "Unsorted".<br/>
- </ul>
- </ul>
- <br>
- <a name="DLNARendererset"></a>
- <b>Set</b>
- <ul>
- <br><code>set <name> stream <value></code><br>
- Set any URL to play.
- </ul>
- <ul>
- <br><code>set <name> on</code><br>
- Starts playing the last stream (reading stream).
- </ul>
- <ul>
- <br><code>set <name> off</code><br>
- Sends stop command to device.
- </ul>
- <ul>
- <br><code>set <name> stop</code><br>
- Stop playback.
- </ul>
- <ul>
- <br><code>set <name> volume 0-100</code><br>
- <code>set <name> volume +/-0-100</code><br>
- Set volume of the device.
- </ul>
- <ul>
- <br><code>set <name> channel 1-10</code><br>
- Start playing channel X which must be configured as channel_X attribute first.<br>
- You can specify your channel also in DIDL-Lite XML format if your player doesn't support plain URIs.
- </ul>
- <ul>
- <br><code>set <name> mute on/off</code><br>
- Mute the device.
- </ul>
- <ul>
- <br><code>set <name> pause</code><br>
- Pause playback of the device. No toggle.
- </ul>
- <ul>
- <br><code>set <name> pauseToggle</code><br>
- Toggle pause/play for the device.
- </ul>
- <ul>
- <br><code>set <name> play</code><br>
- Initiate play command. Only makes your player play if a stream was loaded (currentTrackURI is set).
- </ul>
- <ul>
- <br><code>set <name> next</code><br>
- Play next track.
- </ul>
- <ul>
- <br><code>set <name> previous</code><br>
- Play previous track.
- </ul>
- <ul>
- <br><code>set <name> seek <seconds></code><br>
- Seek to position of track in seconds.
- </ul>
- <ul>
- <br><code>set <name> speak "This is a test. 1 2 3."</code><br>
- Speak the text followed after speak within quotes. Works with Google Translate.
- </ul>
- <ul>
- <br><code>set <name> playEverywhere</code><br>
- Only available for Caskeid players.<br>
- Play current track on all available Caskeid players in sync.
- </ul>
- <ul>
- <br><code>set <name> stopPlayEverywhere</code><br>
- Only available for Caskeid players.<br>
- Stops multiroom audio.
- </ul>
- <ul>
- <br><code>set <name> addUnit <unitName></code><br>
- Only available for Caskeid players.<br>
- Adds unit to multiroom audio session.
- </ul>
- <ul>
- <br><code>set <name> removeUnit <unitName></code><br>
- Only available for Caskeid players.<br>
- Removes unit from multiroom audio session.
- </ul>
- <ul>
- <br><code>set <name> multiRoomVolume 0-100</code><br>
- <code>set <name> multiRoomVolume +/-0-100</code><br>
- Only available for Caskeid players.<br>
- Set volume of all devices within this session.
- </ul>
- <ul>
- <br><code>set <name> enableBTCaskeid</code><br>
- Only available for Caskeid players.<br>
- Activates Bluetooth Caskeid for this device.
- </ul>
- <ul>
- <br><code>set <name> disableBTCaskeid</code><br>
- Only available for Caskeid players.<br>
- Deactivates Bluetooth Caskeid for this device.
- </ul>
- <ul>
- <br><code>set <name> stereo <left> <right> <pairName></code><br>
- Only available for Caskeid players.<br>
- Sets stereo mode for left/right speaker and defines the name of the stereo pair.
- </ul>
- <ul>
- <br><code>set <name> standalone</code><br>
- Only available for Caskeid players.<br>
- Puts the speaker into standalone mode if it was member of a stereo pair before.
- </ul>
- <ul>
- <br><code>set <name> saveGroupAs <groupName></code><br>
- Only available for Caskeid players.<br>
- Saves the current group configuration (e.g. saveGroupAs LivingRoom).
- </ul>
- <ul>
- <br><code>set <name> loadGroup <groupName></code><br>
- Only available for Caskeid players.<br>
- Loads the configuration previously saved (e.g. loadGroup LivingRoom).
- </ul>
- <br>
-
- <a name="DLNARendererattr"></a>
- <b>Attributes</b>
- <ul>
- <br><code>ignoreUDNs</code><br>
- Define list (comma or blank separated) of UDNs which should prevent automatic device creation.<br>
- It is important that uuid: is also part of the UDN and must be included.
- </ul>
- </ul>
- =end html
- =cut
|