98_GOOGLECAST.pm 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  1. #############################################################
  2. #
  3. # GOOGLECAST.pm (c) by Dominik Karall, 2016-2018
  4. # dominik karall at gmail dot com
  5. # $Id: 98_GOOGLECAST.pm 16589 2018-04-11 19:01:32Z dominik $
  6. #
  7. # FHEM module to communicate with Google Cast devices
  8. # e.g. Chromecast Video, Chromecast Audio, Google Home
  9. #
  10. # Version: 2.1.3
  11. #
  12. #############################################################
  13. #
  14. # v2.1.3 - 20180311
  15. # - BUGFIX: fix umlauts for device name readings
  16. #
  17. # v2.1.2 - 20180310
  18. # - BUGFIX: fix speak, play, displayWebsite
  19. # - FEATURE: added German commandref (thx@Sailor)
  20. #
  21. # v2.1.1 - 20180303
  22. # - BUGFIX: fix crash on connection failures
  23. # - BUGFIX: update FHEM socket on pychromecast reconnect
  24. #
  25. # v2.1.0 - 20180218
  26. # - BUGFIX: one more socket_client fix
  27. # - BUGFIX: offline state fix
  28. #
  29. # v2.0.3 - 20180217
  30. # - CHANGE: increase speak limit to 500 characters
  31. #
  32. # v2.0.2 - 20180106
  33. # - FEATURE: support speak command for TTS
  34. # set castdevice speak "Hallo"
  35. # - BUGFIX: fix issues with umlauts in device name
  36. # - BUGFIX: fix one socket issue
  37. # - BUGFIX: fix delay for non youtube-dl links
  38. # - BUGFIX: optimize delay for youtube links
  39. #
  40. # v2.0.1 - 20171209
  41. # - FEATURE: support skip/rewind
  42. # - FEATURE: support displaying websites on Chromecast
  43. #
  44. # v2.0.0 - 20170812
  45. # - CHANGE: renamed to 98_GOOGLECAST.pm
  46. # - CHANGE: removed favoriteName_X attribute, it was never used
  47. # - BUGFIX: updated commandref with further required packages
  48. # - FEATURE: state reading now represents status (online, offline,
  49. # playing, paused, buffering)
  50. # - FEATURE: new readings mediaContentId, mediaCurrentPosition,
  51. # mediaDuration, mediaPlayerState, mediaStreamType
  52. # - BUGFIX: change volume to represent integer values only
  53. #
  54. # v1.0.7 - 20170804
  55. # - BUGFIX: fix reconnection in some cases
  56. #
  57. # v1.0.6 - 20170705
  58. # - BUGFIX: speed up youtube video URL extraction with youtube_dl
  59. # - BUGFIX: fixed one more issue when chromecast offline
  60. # - BUGFIX: improved performance by adding socket to FHEM main loop
  61. #
  62. # v1.0.5 - 20170704
  63. # - BUGFIX: hopefuly fixed the annoying hangs when chromecast offline
  64. # - FEATURE: add presence reading (online/offline)
  65. #
  66. # v1.0.4 - 20170101
  67. # - FEATURE: support all services supported by youtube-dl
  68. # https://github.com/rg3/youtube-dl/blob/master/docs/supportedsites.md
  69. # playlists not yet supported!
  70. # - BUGFIX: support non-blocking chromecast search
  71. #
  72. # v1.0.3 - 20161219
  73. # - FEATURE: support volume
  74. # - FEATURE: add new readings and removed
  75. # castStatus, mediaStatus reading
  76. # - FEATURE: add attribute favoriteURL_[1-5]
  77. # - FEATURE: add playFavorite [1-5] set function
  78. # - FEATURE: retry init chromecast every 10s if not found on startup
  79. # - BUGFIX: support special characters for device name
  80. #
  81. # v1.0.2 - 20161216
  82. # - FEATURE: support play of every mime type which is supported
  83. # by Chromecast (see https://developers.google.com/cast/docs/media)
  84. # including youtube URLs
  85. # - CHANGE: change play* methods to play <url>
  86. # - FEATURE: support very simple .m3u which contain only URL
  87. # - BUGFIX: non-blocking playYoutube
  88. # - BUGFIX: fix play if media player is already running
  89. #
  90. # v1.0.1 - 20161211
  91. # - FEATURE: support playYoutube <youtubelink>
  92. #
  93. # v1.0.0 - 20161015
  94. # - FEATURE: first public release
  95. #
  96. # TODO
  97. # - check spotify integration
  98. # - support youtube playlists
  99. #
  100. # NOTES
  101. # def play_media(self, url, content_type, title=None, thumb=None,
  102. # current_time=0, autoplay=True,
  103. # stream_type=STREAM_TYPE_BUFFERED,
  104. # metadata=None, subtitles=None, subtitles_lang='en-US',
  105. # subtitles_mime='text/vtt', subtitle_id=1):
  106. # """
  107. # Plays media on the Chromecast. Start default media receiver if not
  108. # already started.
  109. # Parameters:
  110. # url: str - url of the media.
  111. # content_type: str - mime type. Example: 'video/mp4'.
  112. # title: str - title of the media.
  113. # thumb: str - thumbnail image url.
  114. # current_time: float - seconds from the beginning of the media
  115. # to start playback.
  116. # autoplay: bool - whether the media will automatically play.
  117. # stream_type: str - describes the type of media artifact as one of the
  118. # following: "NONE", "BUFFERED", "LIVE".
  119. # subtitles: str - url of subtitle file to be shown on chromecast.
  120. # subtitles_lang: str - language for subtitles.
  121. # subtitles_mime: str - mimetype of subtitles.
  122. # subtitle_id: int - id of subtitle to be loaded.
  123. # metadata: dict - media metadata object, one of the following:
  124. # GenericMediaMetadata, MovieMediaMetadata, TvShowMediaMetadata,
  125. # MusicTrackMediaMetadata, PhotoMediaMetadata.
  126. # Docs:
  127. # https://developers.google.com/cast/docs/reference/messages#MediaData
  128. # """
  129. #
  130. #############################################################
  131. package main;
  132. use strict;
  133. use warnings;
  134. use Blocking;
  135. use Encode;
  136. use SetExtensions;
  137. use URI::Escape;
  138. use LWP::UserAgent;
  139. sub GOOGLECAST_Initialize($) {
  140. my ($hash) = @_;
  141. $hash->{DefFn} = 'GOOGLECAST_Define';
  142. $hash->{UndefFn} = 'GOOGLECAST_Undef';
  143. $hash->{GetFn} = 'GOOGLECAST_Get';
  144. $hash->{SetFn} = 'GOOGLECAST_Set';
  145. $hash->{ReadFn} = 'GOOGLECAST_Read';
  146. $hash->{AttrFn} = 'GOOGLECAST_Attribute';
  147. $hash->{AttrList} = "favoriteURL_1 favoriteURL_2 favoriteURL_3 favoriteURL_4 ".
  148. "favoriteURL_5 ".$readingFnAttributes;
  149. Log3 $hash, 3, "GOOGLECAST: GoogleCast v2.1.3";
  150. return undef;
  151. }
  152. sub GOOGLECAST_Define($$) {
  153. my ($hash, $def) = @_;
  154. my @a = split("[ \t]+", $def);
  155. my $name = $a[0];
  156. $hash->{STATE} = "initialized";
  157. if (int(@a) > 3) {
  158. return 'GOOGLECAST: Wrong syntax, must be define <name> GOOGLECAST <device name>';
  159. } elsif(int(@a) == 3) {
  160. Log3 $hash, 3, "GOOGLECAST: $a[2] initializing...";
  161. $hash->{CCNAME} = $a[2];
  162. Log3 $hash, 5, "GOOGLECAST: $a[2] set readings offline";
  163. GOOGLECAST_updateReading($hash, "presence", "offline");
  164. GOOGLECAST_updateReading($hash, "state", "offline");
  165. Log3 $hash, 5, "GOOGLECAST: $a[2] start initDevice";
  166. GOOGLECAST_initDevice($hash);
  167. }
  168. return undef;
  169. }
  170. sub GOOGLECAST_findChromecasts {
  171. my ($string) = @_;
  172. my ($name) = split("\\|", $string);
  173. my $result = "$name";
  174. my @ccResult = GOOGLECAST_PyFindChromecasts();
  175. foreach my $ref_cc (@ccResult) {
  176. my @cc = @$ref_cc;
  177. $result .= "|CCDEVICE|".$cc[0]."|".$cc[1]."|".$cc[2]."|".$cc[3]."|".Encode::encode('UTF-8', $cc[4]);
  178. }
  179. Log3 $name, 4, "GOOGLECAST: search result: $result";
  180. return $result;
  181. }
  182. sub GOOGLECAST_initDevice {
  183. my ($hash) = @_;
  184. my $devName = $hash->{CCNAME};
  185. Log3 $hash, 5, "GOOGLECAST($hash->{NAME}): start findChromecasts BlockingCall";
  186. BlockingCall("GOOGLECAST_findChromecasts", $hash->{NAME}, "GOOGLECAST_findChromecastsResult");
  187. Log3 $hash, 5, "GOOGLECAST($hash->{NAME}): finished findChromecasts BlockingCall";
  188. return undef;
  189. }
  190. sub GOOGLECAST_findChromecastsResult {
  191. my ($string) = @_;
  192. my ($name, @ccResult) = split("\\|", $string);
  193. my $hash = $main::defs{$name};
  194. my $devName = $hash->{CCNAME};
  195. $hash->{helper}{ccdevice} = "";
  196. for my $i (0..$#ccResult) {
  197. if($ccResult[$i] eq "CCDEVICE" and $ccResult[$i+5] eq $devName) {
  198. Log3 $hash, 4, "GOOGLECAST ($hash->{NAME}): init cast device $devName";
  199. eval {
  200. $hash->{helper}{ccdevice} = GOOGLECAST_PyCreateChromecast($ccResult[$i+1],$ccResult[$i+2],$ccResult[$i+3],$ccResult[$i+4],$ccResult[$i+5]);
  201. };
  202. if($@) {
  203. $hash->{helper}{ccdevice} = "";
  204. }
  205. Log3 $hash, 4, "GOOGLECAST ($hash->{NAME}): device initialized";
  206. }
  207. }
  208. if($hash->{helper}{ccdevice} eq "") {
  209. Log3 $hash, 4, "GOOGLECAST: $devName not found, retry in 10s.";
  210. InternalTimer(gettimeofday()+10, "GOOGLECAST_initDevice", $hash, 0);
  211. return undef;
  212. }
  213. Log3 $hash, 3, "GOOGLECAST: $devName initialized successfully";
  214. GOOGLECAST_addSocketToMainloop($hash);
  215. GOOGLECAST_checkConnection($hash);
  216. return undef;
  217. }
  218. sub GOOGLECAST_Attribute($$$$) {
  219. my ($mode, $devName, $attrName, $attrValue) = @_;
  220. if($mode eq "set") {
  221. } elsif($mode eq "del") {
  222. }
  223. return undef;
  224. }
  225. sub GOOGLECAST_Set($@) {
  226. my ($hash, $name, @params) = @_;
  227. my $workType = shift(@params);
  228. my $list = "stop:noArg pause:noArg rewind:noArg skip:noArg quitApp:noArg play playFavorite:1,2,3,4,5 volume:slider,0,1,100 displayWebsite speak";
  229. #get quoted text from params
  230. my $blankParams = join(" ", @params);
  231. my @params2;
  232. while($blankParams =~ /"?((?<!")\S+(?<!")|[^"]+)"?\s*/g) {
  233. push(@params2, $1);
  234. }
  235. @params = @params2;
  236. # check parameters for set function
  237. if($workType eq "?") {
  238. return SetExtensions($hash, $list, $name, $workType, @params);
  239. }
  240. if($workType eq "stop") {
  241. GOOGLECAST_setStop($hash);
  242. } elsif($workType eq "pause") {
  243. GOOGLECAST_setPause($hash);
  244. } elsif($workType eq "play") {
  245. GOOGLECAST_setPlay($hash, $params[0]);
  246. } elsif($workType eq "playFavorite") {
  247. GOOGLECAST_setPlayFavorite($hash, $params[0]);
  248. } elsif($workType eq "quitApp") {
  249. GOOGLECAST_setQuitApp($hash);
  250. } elsif($workType eq "volume") {
  251. GOOGLECAST_setVolume($hash, $params[0]);
  252. } elsif($workType eq "displayWebsite") {
  253. GOOGLECAST_setWebsite($hash, $params[0]);
  254. } elsif($workType eq "rewind") {
  255. GOOGLECAST_setRewind($hash);
  256. } elsif($workType eq "skip") {
  257. GOOGLECAST_setSkip($hash);
  258. } elsif($workType eq "speak") {
  259. GOOGLECAST_setSpeak($hash, $params[0]);
  260. } else {
  261. return SetExtensions($hash, $list, $name, $workType, @params);
  262. }
  263. return undef;
  264. }
  265. ### volume ###
  266. sub GOOGLECAST_setVolume {
  267. my ($hash, $volume) = @_;
  268. $volume = $volume/100;
  269. eval {
  270. $hash->{helper}{ccdevice}->set_volume($volume);
  271. };
  272. }
  273. ### dashcast ###
  274. sub GOOGLECAST_setWebsite {
  275. my ($hash, $url) = @_;
  276. eval {
  277. GOOGLECAST_PyLoadDashCast($hash->{helper}{ccdevice}, $url);
  278. };
  279. }
  280. ### speak ###
  281. sub GOOGLECAST_setSpeak {
  282. my ($hash, $ttsText) = @_;
  283. my $ttsLang = AttrVal($hash->{NAME}, "ttsLanguage", "de");
  284. return "GOOGLECAST: Maximum text length is 500 characters." if(length($ttsText) > 500);
  285. $ttsText = uri_escape($ttsText);
  286. my $ttsUrl = "http://translate.google.com/translate_tts?tl=$ttsLang&client=tw-ob&q=$ttsText";
  287. Log3 $hash, 4, "GOOGLECAST($hash->{NAME}): setSpeak $ttsUrl";
  288. eval {
  289. GOOGLECAST_PyPlayMedia($hash->{helper}{ccdevice}, $ttsUrl, "audio/mpeg");
  290. };
  291. return undef;
  292. }
  293. ### playType ###
  294. sub GOOGLECAST_setPlayType {
  295. my ($hash, $url, $mime) = @_;
  296. Log3 $hash, 4, "GOOGLECAST($hash->{NAME}): setPlayType($url, $mime)";
  297. if($mime =~ m/text\/html/) {
  298. GOOGLECAST_setPlayYtDl($hash, $url);
  299. } else {
  300. eval {
  301. Log3 $hash, 4, "GOOGLECAST($hash->{NAME}): start play_media";
  302. GOOGLECAST_PyPlayMedia($hash->{helper}{ccdevice}, $url, $mime);
  303. };
  304. }
  305. return undef;
  306. }
  307. sub GOOGLECAST_setPlayType_String {
  308. my ($string) = @_;
  309. my ($name, $url, $mime) = split("\\|", $string);
  310. my $hash = $main::defs{$name};
  311. if($mime ne "" && $url ne "") {
  312. GOOGLECAST_setPlayType($hash, $url, $mime);
  313. }
  314. }
  315. ### playMedia ###
  316. sub GOOGLECAST_setPlayMedia {
  317. my ($hash, $url) = @_;
  318. BlockingCall("GOOGLECAST_setPlayMediaBlocking", $hash->{NAME}."|".$url, "GOOGLECAST_setPlayType_String");
  319. return undef;
  320. }
  321. sub GOOGLECAST_setPlayMedia_String {
  322. my ($string) = @_;
  323. my ($name, $videoUrl, $origUrl) = split("\\|", $string);
  324. my $hash = $main::defs{$name};
  325. Log3 $hash, 4, "GOOGLECAST($name): setPlayMedia_String($string)";
  326. if($videoUrl ne "") {
  327. GOOGLECAST_setPlayMedia($hash, $videoUrl);
  328. } else {
  329. Log3 $hash, 3, "GOOGLECAST($name): setPlayMedia_String, youtube-dl couldn't find video";
  330. #GOOGLECAST_setPlayMedia($hash, $origUrl);
  331. }
  332. }
  333. sub GOOGLECAST_setPlayMediaBlocking {
  334. my ($string) = @_;
  335. my ($name, $url) = split("\\|", $string);
  336. #$url = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
  337. #$url = "http://swr-mp3-m-swr3.akacast.akamaistream.net:80/7/720/137136/v1/gnl.akacast.akamaistream.net/swr-mp3-m-swr3";
  338. my $ua = new LWP::UserAgent(agent => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.5) Gecko/20060719 Firefox/1.5.0.5');
  339. $ua->max_size(0);
  340. my $resp = $ua->get($url);
  341. my $mime = $resp->header('Content-Type');
  342. if($mime eq "audio/x-mpegurl") {
  343. $mime = "audio/mpeg";
  344. $url = $resp->decoded_content;
  345. $url =~ s/\R//g;
  346. }
  347. return $name."|".$url."|".$mime;
  348. }
  349. ### playYoutue ###
  350. sub GOOGLECAST_setPlayYtDl {
  351. my ($hash, $ytUrl) = @_;
  352. BlockingCall("GOOGLECAST_setPlayYtDlBlocking", $hash->{NAME}."|".$ytUrl, "GOOGLECAST_setPlayMedia_String");
  353. return undef;
  354. }
  355. sub GOOGLECAST_setPlayYtDlBlocking {
  356. my ($string) = @_;
  357. my ($name, $ytUrl) = split("\\|", $string);
  358. my $videoUrl = "";
  359. eval {
  360. $videoUrl = GOOGLECAST_PyGetYTVideoURL($ytUrl);
  361. };
  362. return $name."|".$videoUrl."|".$ytUrl;
  363. }
  364. ### stop ###
  365. sub GOOGLECAST_setStop {
  366. my ($hash) = @_;
  367. eval {
  368. $hash->{helper}{ccdevice}->{media_controller}->stop();
  369. };
  370. return undef;
  371. }
  372. ### playFavorite ###
  373. sub GOOGLECAST_setPlayFavorite {
  374. my ($hash, $favoriteNr) = @_;
  375. GOOGLECAST_setPlay($hash, AttrVal($hash->{NAME}, "favoriteURL_".$favoriteNr, ""));
  376. return undef;
  377. }
  378. ### play ###
  379. sub GOOGLECAST_setPlay {
  380. my ($hash, $url) = @_;
  381. if(!defined($url)) {
  382. eval {
  383. $hash->{helper}{ccdevice}->{media_controller}->play();
  384. };
  385. return undef;
  386. }
  387. if($url =~ /^http/) {
  388. #support streams are listed here
  389. #https://github.com/rg3/youtube-dl/blob/master/docs/supportedsites.md
  390. GOOGLECAST_setPlayMedia($hash, $url);
  391. } else {
  392. GOOGLECAST_PyPlayYouTube($hash->{helper}{ccdevice}, $url);
  393. }
  394. return undef;
  395. }
  396. ### pause ###
  397. sub GOOGLECAST_setPause {
  398. my ($hash) = @_;
  399. eval {
  400. $hash->{helper}{ccdevice}->{media_controller}->pause();
  401. };
  402. return undef;
  403. }
  404. ### rewind ###
  405. sub GOOGLECAST_setRewind {
  406. my ($hash) = @_;
  407. eval {
  408. $hash->{helper}{ccdevice}->{media_controller}->rewind();
  409. };
  410. return undef;
  411. }
  412. ### skip ###
  413. sub GOOGLECAST_setSkip {
  414. my ($hash) = @_;
  415. eval {
  416. $hash->{helper}{ccdevice}->{media_controller}->seek($hash->{helper}{ccdevice}->{media_controller}->{status}->{duration});
  417. };
  418. return undef;
  419. }
  420. ### quitApp ###
  421. sub GOOGLECAST_setQuitApp {
  422. my ($hash) = @_;
  423. eval {
  424. $hash->{helper}{ccdevice}->quit_app();
  425. };
  426. return undef;
  427. }
  428. sub GOOGLECAST_Undef($) {
  429. my ($hash) = @_;
  430. #remove internal timer
  431. RemoveInternalTimer($hash);
  432. return undef;
  433. }
  434. sub GOOGLECAST_Get($$) {
  435. return undef;
  436. }
  437. sub GOOGLECAST_updateReading {
  438. my ($hash, $readingName, $value) = @_;
  439. my $oldValue = ReadingsVal($hash->{NAME}, $readingName, "");
  440. if(!defined($value)) {
  441. $value = "";
  442. }
  443. if($oldValue ne $value) {
  444. readingsSingleUpdate($hash, $readingName, $value, 1);
  445. }
  446. }
  447. sub GOOGLECAST_newChash {
  448. my ($hash, $socket, $chash) = @_;
  449. $chash->{TYPE} = $hash->{TYPE};
  450. $chash->{UDN} = -1;
  451. $chash->{NR} = $devcount++;
  452. $chash->{phash} = $hash;
  453. $chash->{PNAME} = $hash->{NAME};
  454. $chash->{CD} = $socket;
  455. $chash->{FD} = $socket->fileno();
  456. #$chash->{PORT} = $socket->sockport if( $socket->sockport );
  457. $chash->{TEMPORARY} = 1;
  458. $attr{$chash->{NAME}}{room} = 'hidden';
  459. $defs{$chash->{NAME}} = $chash;
  460. $selectlist{$chash->{NAME}} = $chash;
  461. }
  462. sub GOOGLECAST_addSocketToMainloop {
  463. my ($hash) = @_;
  464. my $sock;
  465. #delete any previous sockets
  466. delete($selectlist{"GOOGLECAST-".$hash->{NAME}});
  467. eval {
  468. $sock = $hash->{helper}{ccdevice}->{socket_client}->get_socket();
  469. if ($sock->fileno() > 0) {
  470. $hash->{helper}{currentsock} = $sock;
  471. }
  472. };
  473. if ($sock->fileno() > 0) {
  474. my $chash = GOOGLECAST_newChash($hash, $sock, {NAME => "GOOGLECAST-".$hash->{NAME}});
  475. }
  476. return undef;
  477. }
  478. sub GOOGLECAST_checkConnection {
  479. my ($hash) = @_;
  480. eval {
  481. Log3 $hash, 5, "GOOGLECAST ($hash->{NAME}): run_once";
  482. $hash->{helper}{ccdevice}->{socket_client}->run_once();
  483. };
  484. if($@ || !defined($selectlist{"GOOGLECAST-".$hash->{NAME}})) {
  485. Log3 $hash, 4, "GOOGLECAST ($hash->{NAME}): checkConnection, connection failure, reconnect...";
  486. delete($selectlist{"GOOGLECAST-".$hash->{NAME}});
  487. $hash->{helper}{ccdevice}->{socket_client}->_cleanup() if(defined($hash->{helper}{ccdevice}->{socket_client}->get_socket()));
  488. GOOGLECAST_initDevice($hash);
  489. GOOGLECAST_updateReading($hash, "presence", "offline");
  490. GOOGLECAST_updateReading($hash, "state", "offline");
  491. return undef;
  492. } else {
  493. #update socket
  494. my $sock = $hash->{helper}{ccdevice}->{socket_client}->get_socket();
  495. if(defined($sock) && $hash->{helper}{currentsock} ne $sock) {
  496. GOOGLECAST_addSocketToMainloop($hash);
  497. Log3 $hash, 4, "GOOGLECAST ($hash->{NAME}): checkConnection, pychromecast reconnected-update fhem socket";
  498. } elsif(!defined($sock)) {
  499. Log3 $hash, 4, "GOOGLECAST ($hash->{NAME}): checkConnection, pychromecast socket=NULL";
  500. }
  501. }
  502. InternalTimer(gettimeofday()+10, "GOOGLECAST_checkConnection", $hash, 0);
  503. return undef;
  504. }
  505. sub GOOGLECAST_Read {
  506. my ($hash) = @_;
  507. my $name = $hash->{NAME};
  508. $hash = $hash->{phash};
  509. return undef if (!defined($hash));
  510. eval {
  511. Log3 $hash, 5, "GOOGLECAST ($hash->{NAME}): run_once";
  512. $hash->{helper}{ccdevice}->{socket_client}->run_once();
  513. };
  514. if($@) {
  515. Log3 $hash, 4, "GOOGLECAST ($hash->{NAME}): connection failure, reconnect...";
  516. eval {
  517. delete($selectlist{$name});
  518. };
  519. $hash->{helper}{ccdevice}->{socket_client}->_cleanup() if(defined($hash->{helper}{ccdevice}->{socket_client}->get_socket()));
  520. GOOGLECAST_initDevice($hash);
  521. GOOGLECAST_updateReading($hash, "presence", "offline");
  522. GOOGLECAST_updateReading($hash, "state", "offline");
  523. return undef;
  524. }
  525. GOOGLECAST_updateReading($hash, "presence", "online");
  526. GOOGLECAST_updateReading($hash, "name", Encode::encode("UTF-8", $hash->{helper}{ccdevice}->{name}));
  527. GOOGLECAST_updateReading($hash, "model", $hash->{helper}{ccdevice}->{model_name});
  528. GOOGLECAST_updateReading($hash, "uuid", $hash->{helper}{ccdevice}->{uuid});
  529. GOOGLECAST_updateReading($hash, "castType", $hash->{helper}{ccdevice}->{cast_type});
  530. GOOGLECAST_updateReading($hash, "model", $hash->{helper}{ccdevice}->{model_name});
  531. GOOGLECAST_updateReading($hash, "appId", $hash->{helper}{ccdevice}->{app_id});
  532. GOOGLECAST_updateReading($hash, "appName", $hash->{helper}{ccdevice}->{app_display_name});
  533. GOOGLECAST_updateReading($hash, "idle", $hash->{helper}{ccdevice}->{is_idle});
  534. my $newStatus = $hash->{helper}{ccdevice}->{media_controller}->{status};
  535. if(defined($newStatus)) {
  536. #GOOGLECAST_updateReading($hash, "mediaStatus", $newStatus);
  537. GOOGLECAST_updateReading($hash, "mediaPlayerState", $newStatus->{player_state});
  538. GOOGLECAST_updateReading($hash, "mediaContentId", $newStatus->{content_id});
  539. GOOGLECAST_updateReading($hash, "mediaDuration", $newStatus->{duration});
  540. GOOGLECAST_updateReading($hash, "mediaCurrentPosition", $newStatus->{current_time});
  541. GOOGLECAST_updateReading($hash, "mediaStreamType", $newStatus->{stream_type});
  542. GOOGLECAST_updateReading($hash, "mediaTitle", $newStatus->{title});
  543. GOOGLECAST_updateReading($hash, "mediaSeriesTitle", $newStatus->{series_title});
  544. GOOGLECAST_updateReading($hash, "mediaSeason", $newStatus->{season});
  545. GOOGLECAST_updateReading($hash, "mediaEpisode", $newStatus->{episode});
  546. GOOGLECAST_updateReading($hash, "mediaArtist", $newStatus->{artist});
  547. GOOGLECAST_updateReading($hash, "mediaAlbum", $newStatus->{album_name});
  548. GOOGLECAST_updateReading($hash, "mediaAlbumArtist", $newStatus->{album_artist});
  549. GOOGLECAST_updateReading($hash, "mediaTrack", $newStatus->{track});
  550. if(length($newStatus->{images}) > 0) {
  551. GOOGLECAST_updateReading($hash, "mediaImage", $newStatus->{images}[0]->{url});
  552. } else {
  553. GOOGLECAST_updateReading($hash, "mediaImage", "");
  554. }
  555. }
  556. my $newCastStatus = $hash->{helper}{ccdevice}->{status};
  557. if(defined($newCastStatus)) {
  558. #GOOGLECAST_updateReading($hash, "castStatus", $newCastStatus);
  559. GOOGLECAST_updateReading($hash, "volume", int($newCastStatus->{volume_level}*100));
  560. }
  561. my $curStatus = ReadingsVal($hash->{NAME}, "mediaPlayerState", "UNKNOWN");
  562. if($curStatus eq "PLAYING") {
  563. GOOGLECAST_updateReading($hash, "state", "playing");
  564. } elsif($curStatus eq "BUFFERING") {
  565. GOOGLECAST_updateReading($hash, "state", "buffering");
  566. } elsif($curStatus eq "PAUSED") {
  567. GOOGLECAST_updateReading($hash, "state", "paused");
  568. } else {
  569. GOOGLECAST_updateReading($hash, "state", ReadingsVal($hash->{NAME}, "presence", "offline"));
  570. }
  571. return undef;
  572. }
  573. use Inline Python => <<'PYTHON_CODE_END';
  574. from __future__ import unicode_literals
  575. import pychromecast
  576. import time
  577. import logging
  578. import youtube_dl
  579. import pychromecast.controllers.dashcast as dashcast
  580. import pychromecast.controllers.youtube as youtube
  581. def GOOGLECAST_PyFindChromecasts():
  582. logging.basicConfig(level=logging.CRITICAL)
  583. return pychromecast.discovery.discover_chromecasts()
  584. def GOOGLECAST_PyCreateChromecast(ip, port, uuid, model_name, friendly_name):
  585. logging.basicConfig(level=logging.CRITICAL)
  586. cast = pychromecast._get_chromecast_from_host((ip.decode("utf-8"), int(port), uuid.decode("utf-8"), model_name.decode("utf-8"), friendly_name.decode("utf-8")), blocking=False, timeout=0.1, tries=1, retry_wait=0.1)
  587. return cast
  588. def GOOGLECAST_PyPlayMedia(cast, url, mime):
  589. logging.basicConfig(level=logging.CRITICAL)
  590. cast.play_media(url.decode("utf-8"), mime.decode("utf-8"))
  591. return undef
  592. def GOOGLECAST_PyGetYTVideoURL(yt_url):
  593. yt_url = yt_url.decode("utf-8")
  594. ydl = youtube_dl.YoutubeDL({'forceurl': True, 'simulate': True, 'quiet': '1', 'no_warnings': '1', 'skip_download': True, 'format': 'best', 'youtube_include_dash_manifest': False})
  595. result = ydl.extract_info(yt_url, download=False)
  596. if 'entries' in result:
  597. # Can be a playlist or a list of videos
  598. video = result['entries'][0]
  599. else:
  600. # Just a video
  601. video = result
  602. video_url = video['url']
  603. return video_url
  604. def GOOGLECAST_PyLoadDashCast(cast, url):
  605. url = url.decode("utf-8")
  606. d = dashcast.DashCastController()
  607. cast.register_handler(d)
  608. d.load_url(url,reload_seconds=60)
  609. def GOOGLECAST_PyPlayYouTube(cast, videoId):
  610. videoId = videoId.decode("utf-8")
  611. yt = youtube.YouTubeController()
  612. cast.register_handler(yt)
  613. yt.play_video(videoId)
  614. PYTHON_CODE_END
  615. 1;
  616. =pod
  617. =item device
  618. =item summary Easily control your Google Cast devices (Video, Audio, Google Home)
  619. =item summary_DE Einfache Steuerung deiner Google Cast Geräte (Video, Audio, Google Home)
  620. =begin html
  621. <a name="GOOGLECAST"></a>
  622. <h3>GOOGLECAST</h3>
  623. <ul>
  624. GOOGLECAST is used to control your Google Cast device<br><br>
  625. <b>Note</b><br>Make sure that python3 is installed. Following packages are required:
  626. <ul>
  627. <li>sudo apt-get install libwww-perl python-enum34 python-dev libextutils-makemaker-cpanfile-perl python3-pip cpanminus</li>
  628. <li>sudo pip3 install pychromecast --upgrade</li>
  629. <li>sudo pip3 install youtube-dl --upgrade</li>
  630. <li>sudo INLINE_PYTHON_EXECUTABLE=/usr/bin/python3 cpanm Inline::Python</li>
  631. </ul>
  632. <br>
  633. <br>
  634. <a name="GOOGLECASTdefine" id="GOOGLECASTdefine"></a>
  635. <b>Define</b>
  636. <ul>
  637. <code>define &lt;name&gt; GOOGLECAST &lt;name&gt;</code><br>
  638. <br>
  639. Example:
  640. <ul>
  641. <code>define livingroom.chromecast GOOGLECAST livingroom</code><br><br>
  642. Wait a few seconds till presence switches to online...<br><br>
  643. <code>set livingroom.chromecast play https://www.youtube.com/watch?v=YE7VzlLtp-4</code><br>
  644. </ul>
  645. <br>
  646. Following media types are supported:<br>
  647. <a href="https://developers.google.com/cast/docs/media">Supported media formats</a><br>
  648. Play with youtube-dl works for following URLs:<br>
  649. <a href="https://rg3.github.io/youtube-dl/supportedsites.html">Supported youtube-dl sites</a><br>
  650. <br>
  651. </ul>
  652. <br>
  653. <a name="GOOGLECASTset" id="GOOGLECASTset"></a>
  654. <b>Set</b>
  655. <ul>
  656. <code>set &lt;name&gt; &lt;command&gt; [&lt;parameter&gt;]</code><br>
  657. The following commands are defined:<br><br>
  658. <ul>
  659. <li><code><b>play</b> URL</code> &nbsp;&nbsp;-&nbsp;&nbsp; play from URL</li>
  660. <li><code><b>play</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; play, like resume if paused previsously</li>
  661. <li><code><b>playFavorite</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; plays URL from favoriteURL_[1-5]</li>
  662. <li><code><b>stop</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; stop, stops current playback</li>
  663. <li><code><b>pause</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; pause</li>
  664. <li><code><b>quitApp</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; quit current application, like YouTube</li>
  665. <li><code><b>skip</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; skip track and play next</li>
  666. <li><code><b>rewind</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; rewind track and play it again</li>
  667. <li><code><b>displayWebsite</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; displayWebsite on Chromecast Video</li>
  668. </ul>
  669. <br>
  670. </ul>
  671. <a name="GOOGLECASTattr" id="GOOGLECASTattr"></a>
  672. <b>Attributes</b>
  673. <ul>
  674. <li><code><b>favoriteURL_[1-5]</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; save URL to play afterwards with playFavorite [1-5]</li>
  675. </ul>
  676. <br>
  677. <a name="GOOGLECASTget" id="GOOGLECASTget"></a>
  678. <b>Get</b>
  679. <ul>
  680. <code>n/a</code>
  681. </ul>
  682. <br>
  683. </ul>
  684. =end html
  685. =begin html_DE
  686. <a name="GOOGLECAST"></a>
  687. <h3>GOOGLECAST</h3>
  688. <ul>
  689. GOOGLECAST wird zur Steueung deines Google Cast Devices verwendet<br><br>
  690. <b>Note</b><br>Es ist sicherzustellen, dass python3 installiert ist. Zus&auml;tzlich werden folgende Pakete ben&ouml;tigt:
  691. <ul>
  692. <li>sudo apt-get install libwww-perl python-enum34 python-dev libextutils-makemaker-cpanfile-perl python3-pip cpanminus</li>
  693. <li>sudo pip3 install pychromecast --upgrade</li>
  694. <li>sudo pip3 install youtube-dl --upgrade</li>
  695. <li>sudo INLINE_PYTHON_EXECUTABLE=/usr/bin/python3 cpanm Inline::Python</li>
  696. </ul>
  697. <br>
  698. <br>
  699. <a name="GOOGLECASTdefine" id="GOOGLECASTdefine"></a>
  700. <b>Define</b>
  701. <ul>
  702. <code>define &lt;name&gt; GOOGLECAST &lt;name&gt;</code><br>
  703. <br>
  704. Beispiel:
  705. <ul>
  706. <code>define livingroom.chromecast GOOGLECAST livingroom</code><br><br>
  707. Warte ein paar Sekunden bis das Ger&auml;t als ONLINE angezeigt wird...<br><br>
  708. <code>set livingroom.chromecast play https://www.youtube.com/watch?v=YE7VzlLtp-4</code><br>
  709. </ul>
  710. <br>
  711. Die folgenden Medienformate werden unterst&uuml;tzt:<br>
  712. <a href="https://developers.google.com/cast/docs/media">Unterst&uuml;tzte Medienformate</a><br>
  713. Das Abspielen mittels youtube-dl funktioniert f&uuml;r die folgenden URLs:<br>
  714. <a href="https://rg3.github.io/youtube-dl/supportedsites.html">Unterst&uuml;tzte youtube-dl - Seiten</a><br>
  715. <br>
  716. </ul>
  717. <br>
  718. <a name="GOOGLECASTset" id="GOOGLECASTset"></a>
  719. <b>Set</b>
  720. <ul>
  721. <code>set &lt;name&gt; &lt;command&gt; [&lt;parameter&gt;]</code><br>
  722. Die folgenden Befehle sind definiert:<br><br>
  723. <ul>
  724. <li><code><b>play</b> URL</code> &nbsp;&nbsp;-&nbsp;&nbsp; Abspielen einer URL</li>
  725. <li><code><b>play</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; Abspielen im Sinne von Wiederaufnahme eines zuvor pausierten Abspielvorgangs</li>
  726. <li><code><b>playFavorite</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; spielt die URL aus den Favoriten favoriteURL_[1-5] ab</li>
  727. <li><code><b>stop</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; unterbricht den augenblicklichen Abspielvorgang</li>
  728. <li><code><b>pause</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; pause</li>
  729. <li><code><b>quitApp</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; schlie&szlig;t die gegenw&auml;rtige Applikation wie beispielsweise YouTube</li>
  730. <li><code><b>skip</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; unterbricht das gegenw&auml;rtige Kapitel bzw. Lied und springt zum N&auml;chsten</li>
  731. <li><code><b>rewind</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; springt zum Anfang des gegenw&auml;rtigen Kapitels bzw. Liedes</li>
  732. <li><code><b>displayWebsite</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; anzeigen einer Webseite auf Chromecast Video</li>
  733. </ul>
  734. <br>
  735. </ul>
  736. <a name="GOOGLECASTattr" id="GOOGLECASTattr"></a>
  737. <b>Attribute</b>
  738. <ul>
  739. <li><code><b>favoriteURL_[1-5]</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; Abspeichern von URL - Favoriten um mittels playFavorite [1-5] - Befehl abgespielt zu werden.</li>
  740. </ul>
  741. <br>
  742. <a name="GOOGLECASTget" id="GOOGLECASTget"></a>
  743. <b>Get</b>
  744. <ul>
  745. <code>n/a</code>
  746. </ul>
  747. <br>
  748. </ul>
  749. =end html_DE
  750. =cut