View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  
23  package org.opencastproject.mediapackage.track;
24  
25  import org.opencastproject.mediapackage.AbstractMediaPackageElement;
26  import org.opencastproject.mediapackage.AudioStream;
27  import org.opencastproject.mediapackage.MediaPackageElementFlavor;
28  import org.opencastproject.mediapackage.MediaPackageException;
29  import org.opencastproject.mediapackage.MediaPackageSerializer;
30  import org.opencastproject.mediapackage.Stream;
31  import org.opencastproject.mediapackage.SubtitleStream;
32  import org.opencastproject.mediapackage.Track;
33  import org.opencastproject.mediapackage.VideoStream;
34  import org.opencastproject.util.Checksum;
35  import org.opencastproject.util.MimeType;
36  import org.opencastproject.util.MimeTypes;
37  import org.opencastproject.util.UnknownFileTypeException;
38  
39  import org.w3c.dom.Document;
40  import org.w3c.dom.Node;
41  
42  import java.net.URI;
43  import java.util.ArrayList;
44  import java.util.List;
45  
46  import javax.xml.bind.annotation.XmlAccessType;
47  import javax.xml.bind.annotation.XmlAccessorType;
48  import javax.xml.bind.annotation.XmlAttribute;
49  import javax.xml.bind.annotation.XmlElement;
50  import javax.xml.bind.annotation.XmlRootElement;
51  import javax.xml.bind.annotation.XmlType;
52  import javax.xml.bind.annotation.adapters.XmlAdapter;
53  
54  /**
55   * This class is the base implementation for a media track, which itself is part of a media package, representing e. g.
56   * the speaker video or the slide presentation movie.
57   */
58  @XmlAccessorType(XmlAccessType.NONE)
59  @XmlType(name = "track", namespace = "http://mediapackage.opencastproject.org")
60  @XmlRootElement(name = "track", namespace = "http://mediapackage.opencastproject.org")
61  public class TrackImpl extends AbstractMediaPackageElement implements Track {
62  
63    /** Serial version UID */
64    private static final long serialVersionUID = -1092781733885994038L;
65  
66    public enum StreamingProtocol {
67      DOWNLOAD,HLS,DASH,HDS,SMOOTH,MMS,RTP,RTSP,RTMP,RTMPE,PNM,PNA,ICY,BITTORENTLIVE,FILE,UNKNOWN
68    }
69  
70    /** The duration in milliseconds */
71    @XmlElement(name = "duration")
72    protected Long duration = null;
73  
74    @XmlElement(name = "audio")
75    protected List<AudioStream> audio = new ArrayList<>();
76  
77    @XmlElement(name = "video")
78    protected List<VideoStream> video = new ArrayList<>();
79  
80    @XmlElement(name = "subtitle")
81    protected List<SubtitleStream> subtitle = new ArrayList<>();
82  
83    @XmlAttribute(name = "transport")
84    protected StreamingProtocol transport = null;
85  
86    @XmlElement(name = "live")
87    protected boolean live;
88  
89    @XmlElement(name = "master", required = false)
90    protected Boolean master = null;
91  
92    @XmlElement(name = "logicalname", required = false) // used to maintain referential integrity for playlists
93    protected String logicalname = null;
94  
95    /** Needed by JAXB */
96    public TrackImpl() {
97      this.elementType = Track.TYPE;
98    }
99  
100   /**
101    * Creates a new track object.
102    *
103    * @param flavor
104    *          the track flavor
105    * @param uri
106    *          the track location
107    * @param checksum
108    *          the track checksum
109    * @param mimeType
110    *          the track mime type
111    */
112   TrackImpl(MediaPackageElementFlavor flavor, MimeType mimeType, URI uri, long size, Checksum checksum) {
113     super(Type.Track, flavor, uri, size, checksum, mimeType);
114   }
115 
116   /**
117    * Creates a new track object for the given file and track type.
118    *
119    * @param flavor
120    *          the track flavor
121    * @param uri
122    *          the track location
123    */
124   TrackImpl(MediaPackageElementFlavor flavor, URI uri) {
125     super(Type.Track, flavor, uri);
126     if (uri != null) {
127       try {
128         this.setMimeType(MimeTypes.fromURI(uri));
129       } catch (UnknownFileTypeException e) { }
130     }
131   }
132 
133   /**
134    * Creates a new track from the given url.
135    *
136    * @param uri
137    *          the track location
138    * @return the track
139    */
140   public static TrackImpl fromURI(URI uri) {
141     return new TrackImpl(null, uri);
142   }
143 
144   /**
145    * Sets the track's duration in milliseconds.
146    *
147    * @param duration
148    *          the duration
149    */
150   public void setDuration(Long duration) {
151     this.duration = duration;
152   }
153 
154   /**
155    * @see org.opencastproject.mediapackage.Track#getDuration()
156    */
157   @Override
158   public Long getDuration() {
159     return duration;
160   }
161 
162   @Override
163   public Stream[] getStreams() {
164     List<Stream> streams = new ArrayList<>(audio.size() + video.size() + subtitle.size());
165     streams.addAll(audio);
166     streams.addAll(video);
167     streams.addAll(subtitle);
168     return streams.toArray(new Stream[0]);
169   }
170 
171   /**
172    * Add a stream to the track.
173    */
174   public void addStream(AbstractStreamImpl stream) {
175     if (stream instanceof AudioStreamImpl) {
176       audio.add((AudioStreamImpl) stream);
177     } else if (stream instanceof VideoStreamImpl) {
178       video.add((VideoStreamImpl) stream);
179     } else if (stream instanceof SubtitleStreamImpl) {
180       subtitle.add((SubtitleStreamImpl) stream);
181     } else {
182       throw new IllegalArgumentException("stream must be either audio or video");
183     }
184   }
185 
186   /**
187    * {@inheritDoc}
188    *
189    * @see org.opencastproject.mediapackage.Track#hasAudio()
190    */
191   @Override
192   public boolean hasAudio() {
193     return audio != null && audio.size() > 0;
194   }
195 
196   /**
197    * {@inheritDoc}
198    *
199    * @see org.opencastproject.mediapackage.Track#hasVideo()
200    */
201   @Override
202   public boolean hasVideo() {
203     return video != null && video.size() > 0;
204   }
205 
206   /**
207    * {@inheritDoc}
208    *
209    * @see org.opencastproject.mediapackage.Track#hasSubtitle()
210    */
211   @Override
212   public boolean hasSubtitle() {
213     return subtitle != null && subtitle.size() > 0;
214   }
215 
216   public List<AudioStream> getAudio() {
217     return audio;
218   }
219 
220   public void setAudio(List<AudioStream> audio) {
221     this.audio = audio;
222   }
223 
224   public List<VideoStream> getVideo() {
225     return video;
226   }
227 
228   public void setVideo(List<VideoStream> video) {
229     this.video = video;
230   }
231 
232   public List<SubtitleStream> getSubtitle() {
233     return subtitle;
234   }
235 
236   public void setSubtitle(List<SubtitleStream> subtitle) {
237     this.subtitle = subtitle;
238   }
239 
240   public void setLive(boolean isLive) {
241     this.live = isLive;
242   }
243 
244   /**
245    * @see org.opencastproject.mediapackage.Track#isLive()
246    */
247   @Override
248   public boolean isLive() {
249     return this.live;
250   }
251 
252   /**
253    *  @return true if it is a master adaptive playlist/manifest
254    */
255   @Override
256   public Boolean isMaster() {
257     return hasMaster() && master;
258   }
259 
260   @Override
261   public void setMaster(Boolean master) {
262     this.master = master;
263   }
264 
265   @Override
266   public boolean hasMaster() {
267     return master != null;
268   }
269 
270   /**
271    * @see org.opencastproject.mediapackage.AbstractMediaPackageElement#toManifest(org.w3c.dom.Document,
272    *      MediaPackageSerializer)
273    */
274   @Override
275   public Node toManifest(Document document, MediaPackageSerializer serializer) throws MediaPackageException {
276     Node node = super.toManifest(document, serializer);
277 
278     // duration
279     if (duration != null && duration >= 0) {
280       Node durationNode = document.createElement("duration");
281       durationNode.appendChild(document.createTextNode(Long.toString(duration)));
282       node.appendChild(durationNode);
283     }
284 
285     Node liveNode = document.createElement("live");
286     liveNode.appendChild(document.createTextNode(Boolean.toString(live)));
287     node.appendChild(liveNode);
288 
289     if (hasMaster()) { // optional - if it is a master adaptive playlist/manifest
290       Node masterNode = document.createElement("master");
291       masterNode.appendChild(document.createTextNode(Boolean.toString(isMaster())));
292       node.appendChild(masterNode);
293     }
294 
295     if (logicalname != null && !logicalname.isEmpty()) { // optional
296       Node nameNode = document.createElement("logicalname");
297       liveNode.appendChild(document.createTextNode(logicalname));
298       node.appendChild(nameNode);
299     }
300 
301     for (Stream s : audio)
302       node.appendChild(s.toManifest(document, serializer));
303     for (Stream s : video)
304       node.appendChild(s.toManifest(document, serializer));
305     for (Stream s : subtitle)
306       node.appendChild(s.toManifest(document, serializer));
307     return node;
308   }
309 
310   /**
311    * This implementation returns the track's mime type.
312    *
313    * @see org.opencastproject.mediapackage.Track#getDescription()
314    */
315   @Override
316   public String getDescription() {
317     StringBuffer buf = new StringBuffer("");
318     /*
319      * todo boolean details = false; if (hasVideo()) { details = true; buf.append(videoSettings); } if (hasAudio()) {
320      * String audioCodec = audioSettings.toString(); if (!hasVideo() || !audioCodec.equals(videoSettings.toString())) {
321      * if (details) buf.append(" / "); details = true; buf.append(audioCodec); } } if (!details) {
322      * buf.append(getMimeType()); }
323      */
324     return buf.toString().toLowerCase();
325   }
326 
327   public void setTransport(StreamingProtocol transport) {
328     this.transport = transport;
329   }
330 
331   public StreamingProtocol getTransport() {
332     if (transport == null) return autodetectTransport(getURI());
333     return transport;
334   }
335 
336   /**
337    * @see java.lang.Object#clone() todo
338    */
339   // @Override
340   // public Object clone() throws CloneNotSupportedException {
341   // TrackImpl t = null;
342   // try {
343   // t = new TrackImpl(flavor, mimeType, new File(path, fileName), checksum);
344   // t.duration = duration;
345   // // todo
346   // //t.audioSettings = (AudioSettings)audioSettings.clone();
347   // //t.videoSettings = (VideoSettings)videoSettings.clone();
348   // } catch (Exception e) {
349   // throw new IllegalStateException("Illegal state while cloning track: " + t);
350   // }
351   // return super.clone();
352   // }
353 
354   public static class Adapter extends XmlAdapter<TrackImpl, Track> {
355     @Override
356     public TrackImpl marshal(Track mp) throws Exception {
357       return (TrackImpl) mp;
358     }
359 
360     @Override
361     public Track unmarshal(TrackImpl mp) throws Exception {
362       return mp;
363     }
364   }
365 
366   private StreamingProtocol autodetectTransport(URI uri) {
367     if (uri == null || uri.getScheme() == null) return null;
368     if (uri.getScheme().toLowerCase().startsWith("http")) {
369         if (uri.getFragment() == null) return StreamingProtocol.DOWNLOAD;
370         else if (uri.getFragment().toLowerCase().endsWith(".m3u8")) return StreamingProtocol.HLS;
371         else if (uri.getFragment().toLowerCase().endsWith(".mpd")) return StreamingProtocol.DASH;
372         else if (uri.getFragment().toLowerCase().endsWith(".f4m")) return StreamingProtocol.HDS;
373         else setTransport(StreamingProtocol.DOWNLOAD);
374     }
375     else if (uri.getScheme().toLowerCase().startsWith("rtmp")) return StreamingProtocol.RTMP;
376     else if (uri.getScheme().toLowerCase().startsWith("rtmpe")) return StreamingProtocol.RTMPE;
377     else if (uri.getScheme().toLowerCase().startsWith("file")) return StreamingProtocol.FILE;
378     else if (uri.getScheme().toLowerCase().startsWith("rtp")) return StreamingProtocol.RTP;
379     else if (uri.getScheme().toLowerCase().startsWith("rtsp")) return StreamingProtocol.RTSP;
380     return StreamingProtocol.UNKNOWN;
381   }
382 
383   @Override
384   public String getLogicalName() {
385     if (logicalname == null) // default to it's own path
386       return uri.getPath();
387     return logicalname;
388   }
389 
390   @Override
391   public void setLogicalName(String name) {
392     logicalname = name;
393   }
394 
395 }