package com.atomjack.vcfp.model; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import com.atomjack.shared.Logger; import com.atomjack.vcfp.VoiceControlForPlexApplication; import com.atomjack.vcfp.net.PlexHttpClient; import org.simpleframework.xml.Attribute; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Root; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.SocketTimeoutException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Root(strict=false) public abstract class PlexMedia implements Parcelable { public enum IMAGE_KEY { NOTIFICATION_THUMB, NOTIFICATION_THUMB_BIG, NOTIFICATION_THUMB_MUSIC, NOTIFICATION_THUMB_MUSIC_BIG, NOTIFICATION_BACKGROUND, NOTIFICATION_MUSIC_BACKGROUND, WEAR_BACKGROUND, LOCAL_VIDEO_BACKGROUND, LOCAL_VIDEO_THUMB, MUSIC_THUMB, MOVIE_THUMB, SHOW_THUMB } public final static Map<IMAGE_KEY, int[]> IMAGE_SIZES = new HashMap<IMAGE_KEY, int[]>() { { put(IMAGE_KEY.NOTIFICATION_THUMB, new int[] {114*2, 64*2}); put(IMAGE_KEY.NOTIFICATION_THUMB_BIG, new int[] {87*2, 128*2}); put(IMAGE_KEY.NOTIFICATION_THUMB_MUSIC, new int[] {128, 128}); put(IMAGE_KEY.NOTIFICATION_THUMB_MUSIC_BIG, new int[] {256, 256}); put(IMAGE_KEY.NOTIFICATION_BACKGROUND, new int[] {600, 960}); put(IMAGE_KEY.NOTIFICATION_MUSIC_BACKGROUND, new int[] {600, 600}); put(IMAGE_KEY.WEAR_BACKGROUND, new int[] {320, 320}); } }; public static final int TYPE_MOVIE = 0; public static final int TYPE_SHOW = 1; public static final int TYPE_MUSIC = 2; public boolean isMovie() { return false; } public boolean isMusic() { return this instanceof PlexTrack; } public abstract boolean isShow(); public boolean isClip() { return false; } public String getType() { if(isMovie()) return "movie"; else if(isMusic()) return "music"; else if(isShow()) return "show"; else if(isClip()) return "clip"; else return "unknown"; } @Attribute(required=false) public String key; @Attribute(required=false) public String ratingKey; @Attribute(required=false) public String title; @Attribute(required=false) public String art; @Attribute(required=false) public String viewOffset = "0"; @Attribute(required=false) public PlexServer server; @Attribute(required=false) public String grandparentKey; @Attribute(required=false) public String grandparentTitle; @Attribute(required=false) public String grandparentThumb; @Attribute(required=false) public String grandparentArt; @Attribute(required=false) public int duration; @Attribute(required=false) public String thumb; @ElementList(required=false, inline=true, entry="Media") public List<Media> media = new ArrayList<Media>(); public int activeSubtitleStream = 0; public String parentArt; public String getTitle() { return title; } // PlexVideo will override this and return the episode title if it's a show, or "" if not public String getEpisodeTitle() { return ""; } public String getSummary() { return ""; } public String getDurationTimecode() { return VoiceControlForPlexApplication.secondsToTimecode(duration/1000); } public String getNotificationTitle() { if(isMusic()) return ((PlexTrack)this).getArtist(); else if(isShow()) return getTitle(); return title; } public String getNotificationSubtitle() { if(isMusic() || isShow()) { return title; } return null; } public InputStream getNotificationThumb(IMAGE_KEY key, Connection connection) { int width; width = IMAGE_SIZES.get(key)[0]; int height; height = IMAGE_SIZES.get(key)[1]; String whichThumb = null; if(isMovie()) { if(width > height) whichThumb = art; else whichThumb = thumb; } else if(isShow()) { if(width > height) whichThumb = art; else whichThumb = grandparentThumb; } else if(isMusic()) { whichThumb = thumb != null ? thumb : grandparentThumb; } Logger.d("whichThumb: %s, width: %d, height: %d, key: %s", whichThumb, width, height, key); return getThumb(width, height, whichThumb, connection); } // Provides the appropriate notification thumb(image) for the supplied image key public String getNotificationThumb(IMAGE_KEY key) { int width = IMAGE_SIZES.get(key)[0]; int height = IMAGE_SIZES.get(key)[1]; String whichThumb = null; if(isMovie()) { if(width > height) whichThumb = art; else whichThumb = thumb; } else if(isShow()) { if(width > height) whichThumb = grandparentArt; else whichThumb = grandparentThumb; } else if(isMusic()) { whichThumb = thumb != null ? thumb : grandparentThumb; } return whichThumb; } public InputStream getThumb(int width, int height, String whichThumb, Connection connection) { if(whichThumb == null) whichThumb = thumb != null ? thumb : grandparentThumb; String path = String.format("/photo/:/transcode?width=%d&height=%d&url=%s", width, height, Uri.encode(String.format("http://127.0.0.1:32400%s", whichThumb))); String url = server.buildURL(connection, path); Logger.d("thumb url: %s", url); try { byte[] imageData = PlexHttpClient.getSyncBytes(url); InputStream is = new ByteArrayInputStream(imageData); is.reset(); return is; } catch (SocketTimeoutException e) { Logger.d("Couldn't get thumb"); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public int describeContents() { return 0; } private List<Stream> streams; public List<Stream> getStreams() { // The list of streams needs to have a "none" subtitle stream added to it (if there is at least // one subtitle stream). Subsequent calls to get the list of streams should get this list, since any // manipulation of which (audio/video) stream is active will need to be saved to it - the list of // streams in media/parts will not reflect the updating of active streams. if (streams == null) { streams = new ArrayList<>(); Media m = media.get(0); Part p = m.parts.get(0); List<Stream> ss = new ArrayList<>(); Stream none = Stream.getNoneSubtitleStream(); none.partId = p.id; for (int i = 0; i < p.streams.size(); i++) { if (p.streams.get(i).streamType == Stream.SUBTITLE && !ss.contains(none)) { ss.add(none); } p.streams.get(i).partId = p.id; ss.add(p.streams.get(i)); } boolean subsActive = false; for (Stream s : ss) { if(s.streamType == Stream.SUBTITLE && s.isActive()) subsActive = true; } if(!subsActive) none.setActive(true); streams = ss; } return streams; } public List<Stream> getStreams(int streamType) { List<Stream> s = new ArrayList<Stream>(); for (Stream stream : getStreams()) { if (stream.streamType == streamType) s.add(stream); } return s; } public Stream getActiveStream(int streamType) { List<Stream> streams = getStreams(streamType); for(Stream stream : streams) { if(stream.isActive()) return stream; } return null; } public void setActiveStream(Stream s) { for(Stream ss : streams) { if(ss.streamType == s.streamType) { ss.setActive(s.id.equals(ss.id)); } } } public Stream getNextStream(int streamType) { List<Stream> tempStreams = getStreams(streamType); if (tempStreams.size() == 0) { return null; } else { int activeIndex = 0; for (int i = 0; i < tempStreams.size(); i++) { if (tempStreams.get(i).isActive()) activeIndex = i; } Logger.d("Active %s stream: %d", (streamType == Stream.SUBTITLE ? "subtitle" : "audio"), activeIndex); int newI = activeIndex + 1 >= tempStreams.size() ? 0 : activeIndex + 1; Stream newStream = tempStreams.get(newI); return newStream; } } public PlexMedia() { } public void writeToParcel(Parcel out, int flags) { out.writeString(key); out.writeString(title); out.writeString(viewOffset); out.writeString(grandparentTitle); out.writeString(grandparentThumb); out.writeString(grandparentArt); out.writeString(parentArt); out.writeString(thumb); out.writeString(art); out.writeInt(duration); out.writeString(ratingKey); out.writeParcelable(server, flags); out.writeTypedList(media); out.writeString(grandparentKey); } public PlexMedia(Parcel in) { key = in.readString(); title = in.readString(); viewOffset = in.readString(); grandparentTitle = in.readString(); grandparentThumb = in.readString(); grandparentArt = in.readString(); parentArt = in.readString(); thumb = in.readString(); art = in.readString(); duration = in.readInt(); ratingKey = in.readString(); server = in.readParcelable(PlexServer.class.getClassLoader()); media = new ArrayList<Media>(); in.readTypedList(media, Media.CREATOR); grandparentKey = in.readString(); } public String getCacheKey(String which) { return String.format("%s%s", server.machineIdentifier, which); } public String getImageKey(IMAGE_KEY imageKey) { if(server == null) return null; else { return String.format("%s/%s/%s", server.machineIdentifier, ratingKey, imageKey); } } @Override public boolean equals(Object o) { if(this == o) return true; if(o == null) return false; if(getClass() != o.getClass()) return false; PlexMedia other = (PlexMedia)o; if(key == null) { if (other.key != null) return false; } else if(!key.equals(other.key)) return false; return true; } @Override public int hashCode() { final int prime = 37; int result = 1; result = prime * result + ((key == null) ? 0 : key.hashCode()); return result; } }