1 /*
2  * Copyright 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.sampletvinput.rich;
18 
19 import com.example.android.sampletvinput.rich.RichTvInputService.ChannelInfo;
20 import com.example.android.sampletvinput.rich.RichTvInputService.ProgramInfo;
21 import com.example.android.sampletvinput.rich.RichTvInputService.TvInput;
22 import com.example.android.sampletvinput.TvContractUtils;
23 import com.example.android.sampletvinput.player.TvInputPlayer;
24 
25 import org.xmlpull.v1.XmlPullParser;
26 import org.xmlpull.v1.XmlPullParserException;
27 
28 import java.io.IOException;
29 import java.text.SimpleDateFormat;
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 /**
34  * A parser for the channels and programs feed used in {@link RichTvInputService}.
35  * <p>
36  * NOTE: The feed format here is just an example. Developers may invent any new formats and parse
37  * them in their own way.
38  * </p>
39  */
40 public class ChannelXMLParser {
41     private static String TAG = "ChannelXmlParser";
42 
43     private static final String TAG_TVINPUTS = "TvInputs";
44     private static final String TAG_CHANNELS = "Channels";
45     private static final String TAG_CHANNEL = "Channel";
46     private static final String TAG_PROGRAM = "Program";
47 
48     private static final String ATTR_TVINPUT_DISPLAY_NAME = "display_name";
49     private static final String ATTR_TVINPUT_NAME = "name";
50     private static final String ATTR_TVINPUT_DESCRIPTION = "description";
51     private static final String ATTR_LOGO_THUMB_URL = "logo_thumb_url";
52     private static final String ATTR_LOGO_BACKGROUND_URL = "logo_background_url";
53 
54     private static final String ATTR_DISPLAY_NUMBER = "display_number";
55     private static final String ATTR_DISPLAY_NAME = "display_name";
56     private static final String ATTR_VIDEO_WIDTH = "video_width";
57     private static final String ATTR_VIDEO_HEIGHT = "video_height";
58     private static final String ATTR_LOGO_URL = "logo_url";
59 
60     private static final String ATTR_TITLE = "title";
61     private static final String ATTR_POSTER_ART_URL = "poster_art_url";
62     private static final String ATTR_DURATION_SEC = "duration_sec";
63     private static final String ATTR_VIDEO_URL = "video_url";
64     private static final String ATTR_VIDEO_TYPE = "video_type";
65     private static final String ATTR_DESCRIPTION = "description";
66     private static final String ATTR_CONTENT_RATING = "content_rating";
67 
68     private static final String VALUE_VIDEO_TYPE_HTTP_PROGRESSIVE = "HTTP_PROGRESSIVE";
69     private static final String VALUE_VIDEO_TYPE_HLS = "HLS";
70     private static final String VALUE_VIDEO_TYPE_MPEG_DASH = "MPEG_DASH";
71 
parseTvInput(XmlPullParser parser)72     public static TvInput parseTvInput(XmlPullParser parser)
73             throws XmlPullParserException, IOException {
74         parser.nextTag();
75         parser.require(XmlPullParser.START_TAG, null, TAG_TVINPUTS);
76         return parseTvInputDetail(parser);
77     }
78 
parseTvInputDetail(XmlPullParser parser)79     private static TvInput parseTvInputDetail(XmlPullParser parser) {
80         String displayName = null;
81         String name = null;
82         String description = null;
83         String logoThumbUrl = null;
84         String logoBackgroundUrl = null;
85         for (int i = 0; i < parser.getAttributeCount(); ++i) {
86             String attr = parser.getAttributeName(i);
87             String value = parser.getAttributeValue(i);
88             if (ATTR_TVINPUT_DISPLAY_NAME.equals(attr)) {
89                 displayName = value;
90             } else if (ATTR_TVINPUT_NAME.equals(attr)) {
91                 name = value;
92             } else if (ATTR_TVINPUT_DESCRIPTION.equals(attr)) {
93                 description = value;
94             } else if (ATTR_LOGO_THUMB_URL.equals(attr)) {
95                 logoThumbUrl = value;
96             } else if (ATTR_LOGO_BACKGROUND_URL.equals(attr)) {
97                 logoBackgroundUrl = value;
98             }
99         }
100         return new TvInput(displayName, name, description, logoThumbUrl, logoBackgroundUrl);
101     }
102 
parseChannelXML(XmlPullParser parser)103     public static List<ChannelInfo> parseChannelXML(XmlPullParser parser)
104             throws XmlPullParserException, IOException {
105         List<ChannelInfo> list = new ArrayList<ChannelInfo>();
106         parser.nextTag();
107 
108         parser.require(XmlPullParser.START_TAG, null, TAG_CHANNELS);
109         while (parser.next() != XmlPullParser.END_DOCUMENT) {
110             if (parser.getEventType() == XmlPullParser.START_TAG
111                     && TAG_CHANNEL.equals(parser.getName())) {
112                 list.add(parseChannel(parser));
113             }
114         }
115         return list;
116     }
117 
parseChannel(XmlPullParser parser)118     private static ChannelInfo parseChannel(XmlPullParser parser)
119             throws XmlPullParserException, IOException {
120         String displayNumber = null;
121         String displayName = null;
122         int videoWidth = 0;
123         int videoHeight = 0;
124         String logoUrl = null;
125         StringBuilder hashString = new StringBuilder();
126         for (int i = 0; i < parser.getAttributeCount(); ++i) {
127             String attr = parser.getAttributeName(i);
128             String value = parser.getAttributeValue(i);
129             // Here we assume that other metadata except number and name may change when the feed
130             // is updated.
131             if (ATTR_DISPLAY_NUMBER.equals(attr)) {
132                 displayNumber = value;
133                 hashString.append(value).append(";");
134             } else if (ATTR_DISPLAY_NAME.equals(attr)) {
135                 displayName = value;
136                 hashString.append(value).append(";");
137             } else if (ATTR_VIDEO_WIDTH.equals(attr)) {
138                 videoWidth = Integer.parseInt(value);
139             } else if (ATTR_VIDEO_HEIGHT.equals(attr)) {
140                 videoHeight = Integer.parseInt(value);
141             } else if (ATTR_LOGO_URL.equals(attr)) {
142                 logoUrl = value;
143             }
144         }
145         List<ProgramInfo> programs = new ArrayList<>();
146         while (parser.next() != XmlPullParser.END_DOCUMENT) {
147             if (parser.getEventType() == XmlPullParser.START_TAG) {
148                 if (TAG_PROGRAM.equals(parser.getName())) {
149                     programs.add(parseProgram(parser));
150                 }
151             } else if (TAG_CHANNEL.equals(parser.getName())
152                     && parser.getEventType() == XmlPullParser.END_TAG) {
153                 break;
154             }
155         }
156         // Developers should assign original network ID in the right way not using the fake ID.
157         int fakeOriginalNetworkId = hashString.toString().hashCode();
158         return new ChannelInfo(displayNumber, displayName, logoUrl, fakeOriginalNetworkId, 0, 0,
159                 videoWidth, videoHeight, programs);
160     }
161 
parseProgram(XmlPullParser parser)162     private static ProgramInfo parseProgram(XmlPullParser parser) {
163         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
164         String title = null;
165         long durationSec = 0;
166         String videoUrl = null;
167         int videoType = TvInputPlayer.SOURCE_TYPE_HTTP_PROGRESSIVE;
168         String description = null;
169         String posterArtUri = null;
170         String contentRatings = null;
171         for (int i = 0; i < parser.getAttributeCount(); ++i) {
172             String attr = parser.getAttributeName(i);
173             String value = parser.getAttributeValue(i);
174             if (ATTR_TITLE.equals(attr)) {
175                 title = value;
176             } else if (ATTR_POSTER_ART_URL.equals(attr)) {
177                 posterArtUri = value;
178             } else if (ATTR_DURATION_SEC.equals(attr)) {
179                 durationSec = Integer.parseInt(value);
180             } else if (ATTR_VIDEO_URL.equals(attr)) {
181                 videoUrl = value;
182             } else if (ATTR_VIDEO_TYPE.equals(attr)) {
183                 if (VALUE_VIDEO_TYPE_HTTP_PROGRESSIVE.equals(value)) {
184                     videoType = TvInputPlayer.SOURCE_TYPE_HTTP_PROGRESSIVE;
185                 } else if (VALUE_VIDEO_TYPE_HLS.equals(value)) {
186                     videoType = TvInputPlayer.SOURCE_TYPE_HLS;
187                 } else if (VALUE_VIDEO_TYPE_MPEG_DASH.equals(value)) {
188                     videoType = TvInputPlayer.SOURCE_TYPE_MPEG_DASH;
189                 }
190             } else if (ATTR_DESCRIPTION.equals(attr)) {
191                 description = value;
192             } else if (ATTR_CONTENT_RATING.equals(attr)) {
193                 contentRatings = value;
194             }
195         }
196         return new ProgramInfo(title, posterArtUri, description, durationSec,
197                 TvContractUtils.stringToContentRatings(contentRatings), videoUrl, videoType, 0);
198     }
199 }
200