2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "HTSPSession.h"
23 #include "video/VideoInfoTag.h"
25 #include "utils/log.h"
26 #include "utils/StringUtils.h"
27 #include "settings/AdvancedSettings.h"
29 #include <netinet/in.h>
30 #include <netinet/tcp.h>
31 #include <sys/socket.h>
34 #include "libhts/net.h"
35 #include "libhts/htsmsg.h"
36 #include "libhts/htsmsg_binary.h"
37 #include "libhts/sha1.h"
47 static const SContentType g_dvb_content_group[] =
48 { { 0x1, "Movie/Drama" }
49 , { 0x2, "News/Current Affairs" }
50 , { 0x3, "Show/Game show" }
52 , { 0x5, "Children's/Youth" }
53 , { 0x6, "Music/Ballet/Dance" }
54 , { 0x7, "Arts/Culture (without music)" }
55 , { 0x8, "Social/Political issues/Economics" }
56 , { 0x9, "Childrens/Youth Education/Science/Factual" }
57 , { 0xa, "Leisure hobbies" }
62 static const SContentType g_dvb_content_type[] =
65 { 0x11, "Detective/Thriller" }
66 , { 0x12, "Adventure/Western/War" }
67 , { 0x13, "Science Fiction/Fantasy/Horror" }
69 , { 0x15, "Soap/Melodrama/Folkloric" }
71 , { 0x17, "Serious/ClassicalReligion/Historical" }
72 , { 0x18, "Adult Movie/Drama" }
74 // news/current affairs
75 , { 0x21, "News/Weather Report" }
76 , { 0x22, "Magazine" }
77 , { 0x23, "Documentary" }
78 , { 0x24, "Discussion/Interview/Debate" }
81 , { 0x31, "Game show/Quiz/Contest" }
86 , { 0x41, "Special Event (Olympics/World cup/...)" }
87 , { 0x42, "Magazine" }
88 , { 0x43, "Football/Soccer" }
89 , { 0x44, "Tennis/Squash" }
90 , { 0x45, "Team sports (excluding football)" }
91 , { 0x46, "Athletics" }
92 , { 0x47, "Motor Sport" }
93 , { 0x48, "Water Sport" }
94 , { 0x49, "Winter Sports" }
95 , { 0x4a, "Equestrian" }
96 , { 0x4b, "Martial sports" }
99 , { 0x51, "Pre-school" }
100 , { 0x52, "Entertainment (6 to 14 year-olds)" }
101 , { 0x53, "Entertainment (10 to 16 year-olds)" }
102 , { 0x54, "Informational/Educational/Schools" }
103 , { 0x55, "Cartoons/Puppets" }
105 // music/ballet/dance
106 , { 0x61, "Rock/Pop" }
107 , { 0x62, "Serious music/Classical Music" }
108 , { 0x63, "Folk/Traditional music" }
110 , { 0x65, "Musical/Opera" }
114 , { 0x71, "Performing Arts" }
115 , { 0x72, "Fine Arts" }
116 , { 0x73, "Religion" }
117 , { 0x74, "Popular Culture/Tradital Arts" }
118 , { 0x75, "Literature" }
119 , { 0x76, "Film/Cinema" }
120 , { 0x77, "Experimental Film/Video" }
121 , { 0x78, "Broadcasting/Press" }
122 , { 0x79, "New Media" }
123 , { 0x7a, "Magazine" }
124 , { 0x7b, "Fashion" }
126 // social/political/economic
127 , { 0x81, "Magazine/Report/Domentary" }
128 , { 0x82, "Economics/Social Advisory" }
129 , { 0x83, "Remarkable People" }
131 // children's youth: educational/science/factual
132 , { 0x91, "Nature/Animals/Environment" }
133 , { 0x92, "Technology/Natural sciences" }
134 , { 0x93, "Medicine/Physiology/Psychology" }
135 , { 0x94, "Foreign Countries/Expeditions" }
136 , { 0x95, "Social/Spiritual Sciences" }
137 , { 0x96, "Further Education" }
138 , { 0x97, "Languages" }
141 , { 0xa1, "Tourism/Travel" }
142 , { 0xa2, "Handicraft" }
143 , { 0xa3, "Motoring" }
144 , { 0xa4, "Fitness & Health" }
145 , { 0xa5, "Cooking" }
146 , { 0xa6, "Advertisement/Shopping" }
147 , { 0xa7, "Gardening" }
150 , { 0xb0, "Original Language" }
151 , { 0xb1, "Black and White" }
152 , { 0xb2, "Unpublished" }
153 , { 0xb3, "Live Broadcast" }
157 using namespace HTSP;
159 string CHTSPSession::GetGenre(unsigned type)
161 // look for full content
162 for(unsigned int i = 0; i < sizeof(g_dvb_content_type) / sizeof(g_dvb_content_type[0]); i++)
164 if(g_dvb_content_type[i].id == type)
165 return g_dvb_content_type[i].genre;
169 type = (type >> 4) & 0xf;
170 for(unsigned int i = 0; i < sizeof(g_dvb_content_group) / sizeof(g_dvb_content_group[0]); i++)
172 if(g_dvb_content_group[i].id == type)
173 return g_dvb_content_group[i].genre;
179 CHTSPSession::CHTSPSession()
180 : m_fd(INVALID_SOCKET)
189 CHTSPSession::~CHTSPSession()
194 void CHTSPSession::Abort()
196 shutdown(m_fd, SHUT_RDWR);
199 void CHTSPSession::Close()
201 if(m_fd != INVALID_SOCKET)
204 m_fd = INVALID_SOCKET;
215 bool CHTSPSession::Connect(const std::string& hostname, int port)
218 int errlen = sizeof(errbuf);
220 // const char *method;
221 const char *server, *version;
222 const void * chall = NULL;
223 size_t chall_len = 0;
229 m_fd = htsp_tcp_connect(hostname.c_str()
231 , errbuf, errlen, 3000);
232 if(m_fd == INVALID_SOCKET)
234 CLog::Log(LOGERROR, "CHTSPSession::Open - failed to connect to server (%s)\n", errbuf);
239 m = htsmsg_create_map();
240 htsmsg_add_str(m, "method", "hello");
241 htsmsg_add_str(m, "clientname", "XBMC Media Center");
242 htsmsg_add_u32(m, "htspversion", 1);
245 if((m = ReadResult(m)) == NULL)
247 CLog::Log(LOGERROR, "CHTSPSession::Open - failed to read greeting from server");
250 // method = htsmsg_get_str(m, "method");
251 htsmsg_get_s32(m, "htspversion", &proto);
252 server = htsmsg_get_str(m, "servername");
253 version = htsmsg_get_str(m, "serverversion");
254 htsmsg_get_bin(m, "challenge", &chall, &chall_len);
256 CLog::Log(LOGDEBUG, "CHTSPSession::Open - connected to server: [%s], version: [%s], proto: %d"
257 , server ? server : "", version ? version : "", proto);
261 if(chall && chall_len)
263 m_challenge = malloc(chall_len);
264 m_challenge_len = chall_len;
265 memcpy(m_challenge, chall, chall_len);
272 bool CHTSPSession::Auth(const std::string& username, const std::string& password)
274 htsmsg_t *m = htsmsg_create_map();
275 htsmsg_add_str(m, "method" , "authenticate");
276 htsmsg_add_str(m, "username", username.c_str());
278 if(password != "" && m_challenge)
280 struct HTSSHA1* shactx = (struct HTSSHA1*) malloc(hts_sha1_size);
282 hts_sha1_init(shactx);
283 hts_sha1_update(shactx
284 , (const uint8_t *)password.c_str()
285 , password.length());
286 hts_sha1_update(shactx
287 , (const uint8_t *)m_challenge
289 hts_sha1_final(shactx, d);
290 htsmsg_add_bin(m, "digest", d, 20);
294 return ReadSuccess(m, false, "get reply from authentication with server");
297 htsmsg_t* CHTSPSession::ReadMessage(int timeout)
305 htsmsg_t* m = m_queue.front();
310 x = htsp_tcp_read_timeout(m_fd, &l, 4, timeout);
312 return htsmsg_create_map();
316 CLog::Log(LOGERROR, "CHTSPSession::ReadMessage - Failed to read packet size (%d)\n", x);
322 return htsmsg_create_map();
326 x = htsp_tcp_read(m_fd, buf, l);
329 CLog::Log(LOGERROR, "CHTSPSession::ReadMessage - Failed to read packet (%d)\n", x);
334 return htsmsg_binary_deserialize(buf, l, buf); /* consumes 'buf' */
337 bool CHTSPSession::SendMessage(htsmsg_t* m)
342 if(htsmsg_binary_serialize(m, &buf, &len, -1) < 0)
349 if(send(m_fd, (char*)buf, len, 0) < 0)
358 htsmsg_t* CHTSPSession::ReadResult(htsmsg_t* m, bool sequence)
361 htsmsg_add_u32(m, "seq", ++m_seq);
366 std::deque<htsmsg_t*> queue;
369 while((m = ReadMessage()))
374 if(!htsmsg_get_u32(m, "seq", &seq) && seq == m_seq)
378 if(queue.size() >= m_queue_size)
380 CLog::Log(LOGERROR, "CDVDInputStreamHTSP::ReadResult - maximum queue size (%u) reached", m_queue_size);
389 if(m && (error = htsmsg_get_str(m, "error")))
391 CLog::Log(LOGERROR, "CDVDInputStreamHTSP::ReadResult - error (%s)", error);
396 if(m && !htsmsg_get_u32(m, "noaccess", &noaccess) && noaccess)
398 CLog::Log(LOGERROR, "CDVDInputStreamHTSP::ReadResult - access denied (%d)", noaccess);
406 bool CHTSPSession::ReadSuccess(htsmsg_t* m, bool sequence, std::string action)
408 if((m = ReadResult(m, sequence)) == NULL)
410 CLog::Log(LOGDEBUG, "CDVDInputStreamHTSP::ReadSuccess - failed to %s", action.c_str());
417 bool CHTSPSession::SendSubscribe(int subscription, int channel)
419 htsmsg_t *m = htsmsg_create_map();
420 htsmsg_add_str(m, "method" , "subscribe");
421 htsmsg_add_s32(m, "channelId" , channel);
422 htsmsg_add_s32(m, "subscriptionId", subscription);
423 return ReadSuccess(m, true, "subscribe to channel");
426 bool CHTSPSession::SendUnsubscribe(int subscription)
428 htsmsg_t *m = htsmsg_create_map();
429 htsmsg_add_str(m, "method" , "unsubscribe");
430 htsmsg_add_s32(m, "subscriptionId", subscription);
431 return ReadSuccess(m, true, "unsubscribe from channel");
434 bool CHTSPSession::SendEnableAsync()
436 htsmsg_t *m = htsmsg_create_map();
437 htsmsg_add_str(m, "method", "enableAsyncMetadata");
438 return ReadSuccess(m, true, "enableAsyncMetadata failed");
441 bool CHTSPSession::GetEvent(SEvent& event, uint32_t id)
449 htsmsg_t *msg = htsmsg_create_map();
450 htsmsg_add_str(msg, "method", "getEvent");
451 htsmsg_add_u32(msg, "eventId", id);
452 if((msg = ReadResult(msg, true)) == NULL)
454 CLog::Log(LOGDEBUG, "CHTSPSession::GetEvent - failed to get event %d", id);
457 return ParseEvent(msg, id, event);
460 bool CHTSPSession::ParseEvent(htsmsg_t* msg, uint32_t id, SEvent &event)
462 uint32_t start, stop, next, content;
463 const char *title, *desc;
464 if( htsmsg_get_u32(msg, "start", &start)
465 || htsmsg_get_u32(msg, "stop" , &stop)
466 || (title = htsmsg_get_str(msg, "title")) == NULL)
468 CLog::Log(LOGDEBUG, "CHTSPSession::ParseEvent - malformed event");
478 if((desc = htsmsg_get_str(msg, "description")))
480 if(htsmsg_get_u32(msg, "nextEventId", &next))
485 if(htsmsg_get_u32(msg, "contentType", &content) == 0)
486 event.content = content;
488 CLog::Log(LOGDEBUG, "CHTSPSession::ParseEvent - id:%u, title:'%s', desc:'%s', start:%u, stop:%u, next:%u, content:%u"
490 , event.title.c_str()
491 , event.descs.c_str()
500 void CHTSPSession::ParseChannelUpdate(htsmsg_t* msg, SChannels &channels)
502 uint32_t id, event = 0, num = 0;
503 const char *name, *icon;
504 if(htsmsg_get_u32(msg, "channelId", &id))
506 CLog::Log(LOGERROR, "CHTSPSession::ParseChannelUpdate - malformed message received");
511 SChannel &channel = channels[id];
514 if(htsmsg_get_u32(msg, "eventId", &event) == 0)
515 channel.event = event;
517 if((name = htsmsg_get_str(msg, "channelName")))
520 if((icon = htsmsg_get_str(msg, "channelIcon")))
523 if(htsmsg_get_u32(msg, "channelNumber", &num) == 0)
526 channel.num = id + 1000;
531 channel.num = id; // fallback older servers
535 if((tags = htsmsg_get_list(msg, "tags")))
537 channel.tags.clear();
540 HTSMSG_FOREACH(f, tags)
542 if(f->hmf_type != HMF_S64)
544 channel.tags.push_back((int)f->hmf_s64);
549 CLog::Log(LOGDEBUG, "CHTSPSession::ParseChannelUpdate - id:%u, name:'%s', icon:'%s', event:%u"
550 , id, name ? name : "(null)", icon ? icon : "(null)", event);
553 void CHTSPSession::ParseChannelRemove(htsmsg_t* msg, SChannels &channels)
556 if(htsmsg_get_u32(msg, "channelId", &id))
558 CLog::Log(LOGERROR, "CDVDInputStreamHTSP::ParseChannelRemove - malformed message received");
562 CLog::Log(LOGDEBUG, "CHTSPSession::ParseChannelRemove - id:%u", id);
567 void CHTSPSession::ParseTagUpdate(htsmsg_t* msg, STags &tags)
570 const char *name, *icon;
571 if(htsmsg_get_u32(msg, "tagId", &id))
573 CLog::Log(LOGERROR, "CHTSPSession::ParseTagUpdate - malformed message received");
577 STag &tag = tags[id];
580 if((icon = htsmsg_get_str(msg, "tagIcon")))
583 if((name = htsmsg_get_str(msg, "tagName")))
588 if((channels = htsmsg_get_list(msg, "members")))
590 tag.channels.clear();
593 HTSMSG_FOREACH(f, channels)
595 if(f->hmf_type != HMF_S64)
597 tag.channels.push_back((int)f->hmf_s64);
601 CLog::Log(LOGDEBUG, "CHTSPSession::ParseTagUpdate - id:%u, name:'%s', icon:'%s'"
602 , id, name ? name : "(null)", icon ? icon : "(null)");
606 void CHTSPSession::ParseTagRemove(htsmsg_t* msg, STags &tags)
609 if(htsmsg_get_u32(msg, "tagId", &id))
611 CLog::Log(LOGERROR, "CHTSPSession::ParseTagRemove - malformed message received");
615 CLog::Log(LOGDEBUG, "CHTSPSession::ParseTagRemove - id:%u", id);
620 bool CHTSPSession::ParseItem(const SChannel& channel, int tagid, const SEvent& event, CFileItem& item)
622 CVideoInfoTag* tag = item.GetVideoInfoTag();
624 CURL url(item.GetPath());
625 CStdString temp = StringUtils::Format("tags/%d/%d.ts", tagid, channel.id);
626 url.SetFileName(temp);
630 tag->m_iTrack = channel.num;
631 tag->m_strAlbum = channel.name;
632 tag->m_strShowTitle = event.title;
633 tag->m_strPlot = event.descs;
634 tag->m_strStatus = "livetv";
635 tag->m_genre = StringUtils::Split(GetGenre(event.content), g_advancedSettings.m_videoItemSeparator);
637 tag->m_strTitle = tag->m_strAlbum;
638 if(tag->m_strShowTitle.length() > 0)
639 tag->m_strTitle += " : " + tag->m_strShowTitle;
641 item.SetPath(url.Get());
642 item.m_strTitle = tag->m_strTitle;
643 item.SetArt("thumb", channel.icon);
644 item.SetMimeType("video/X-htsp");
648 bool CHTSPSession::ParseQueueStatus (htsmsg_t* msg, SQueueStatus &queue)
650 if(htsmsg_get_u32(msg, "packets", &queue.packets)
651 || htsmsg_get_u32(msg, "bytes", &queue.bytes)
652 || htsmsg_get_u32(msg, "Bdrops", &queue.bdrops)
653 || htsmsg_get_u32(msg, "Pdrops", &queue.pdrops)
654 || htsmsg_get_u32(msg, "Idrops", &queue.idrops))
656 CLog::Log(LOGERROR, "CHTSPSession::ParseQueueStatus - malformed message received");
661 /* delay isn't always transmitted */
662 if(htsmsg_get_u32(msg, "delay", &queue.delay))