├── src
└── org
│ └── tvheadend
│ └── tvhguide
│ ├── .gitignore
│ ├── intent
│ ├── SearchIMDbIntent.java
│ └── SearchEPGIntent.java
│ ├── htsp
│ ├── HTSListener.java
│ ├── HTSResponseHandler.java
│ ├── HTSConnectionListener.java
│ ├── HTSFileInputStream.java
│ ├── SelectionThread.java
│ ├── HTSConnection.java
│ └── HTSMessage.java
│ ├── model
│ ├── SeriesInfo.java
│ ├── HttpTicket.java
│ ├── Packet.java
│ ├── ChannelTag.java
│ ├── Subscription.java
│ ├── Stream.java
│ ├── Programme.java
│ ├── Recording.java
│ └── Channel.java
│ ├── TVHVideoView.java
│ ├── SettingsActivity.java
│ ├── ChannelListViewWrapper.java
│ ├── ExternalPlaybackActivity.java
│ ├── RecordingActivity.java
│ ├── PlaybackActivity.java
│ ├── ProgrammeActivity.java
│ ├── TVHGuideApplication.java
│ ├── ChannelListActivity.java
│ └── RecordingListActivity.java
├── res
├── drawable
│ ├── logo.png
│ ├── ic_error.png
│ ├── ic_rec.png
│ ├── logo_72.png
│ ├── ic_success.png
│ ├── ic_rec_small.png
│ ├── ic_schedule.png
│ ├── ic_error_small.png
│ ├── ic_schedule_small.png
│ ├── ic_success_small.png
│ ├── overlay_box.xml
│ └── ic_background.xml
├── drawable-hdpi
│ └── ic_menu_tags.png
├── drawable-ldpi
│ └── ic_menu_tags.png
├── drawable-mdpi
│ └── ic_menu_tags.png
├── xml
│ ├── searchable.xml
│ └── preferences.xml
├── menu
│ ├── pr_menu.xml
│ ├── rc_menu.xml
│ └── main_menu.xml
├── values
│ ├── styles.xml
│ ├── arrays.xml
│ └── strings.xml
├── layout
│ ├── programme_title.xml
│ ├── recording_title.xml
│ ├── recording_list_title.xml
│ ├── recording_layout.xml
│ ├── programme_list_title.xml
│ ├── search_result_title.xml
│ ├── player_layout.xml
│ ├── programme_list_widget.xml
│ ├── channel_list_title.xml
│ ├── search_result_widget.xml
│ ├── channel_list_widget.xml
│ ├── recording_list_widget.xml
│ └── programme_layout.xml
├── values-de
│ └── strings.xml
├── values-cs
│ └── strings.xml
└── values-sv
│ └── strings.xml
├── .gitignore
├── README
├── default.properties
├── project.properties
├── ant.properties
├── proguard.cfg
└── AndroidManifest.xml
/src/org/tvheadend/tvhguide/.gitignore:
--------------------------------------------------------------------------------
1 | R.java
2 |
--------------------------------------------------------------------------------
/res/drawable/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable/logo.png
--------------------------------------------------------------------------------
/res/drawable/ic_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable/ic_error.png
--------------------------------------------------------------------------------
/res/drawable/ic_rec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable/ic_rec.png
--------------------------------------------------------------------------------
/res/drawable/logo_72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable/logo_72.png
--------------------------------------------------------------------------------
/res/drawable/ic_success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable/ic_success.png
--------------------------------------------------------------------------------
/res/drawable/ic_rec_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable/ic_rec_small.png
--------------------------------------------------------------------------------
/res/drawable/ic_schedule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable/ic_schedule.png
--------------------------------------------------------------------------------
/res/drawable/ic_error_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable/ic_error_small.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_tags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable-hdpi/ic_menu_tags.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_tags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable-ldpi/ic_menu_tags.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_tags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable-mdpi/ic_menu_tags.png
--------------------------------------------------------------------------------
/res/drawable/ic_schedule_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable/ic_schedule_small.png
--------------------------------------------------------------------------------
/res/drawable/ic_success_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-tornblom/TVHGuide/HEAD/res/drawable/ic_success_small.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | dist
3 | nbproject/private
4 | libs
5 | *~
6 | *.o
7 | *.d
8 | *.so
9 | *.a
10 | /gen/
11 | /bin/
12 | /nbandroid/
13 | local.properties
14 |
--------------------------------------------------------------------------------
/res/xml/searchable.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/res/menu/pr_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/res/menu/rc_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | TVHGuide is an Android application that enables you to interact with a
2 | TVHeadend server. It allows you to browse programme listings, schedual
3 | recordings and search the EPG. Depending on your hardware, it can also
4 | do some actual playback of either live TV or a recorded stream.
5 |
6 | It is licensed under the GPLv3 (see COPYING).
7 |
--------------------------------------------------------------------------------
/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/default.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "build.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=android-7
12 |
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=android-7
12 |
--------------------------------------------------------------------------------
/res/drawable/overlay_box.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/ant.properties:
--------------------------------------------------------------------------------
1 | # This file is used to override default values used by the Ant build system.
2 | #
3 | # This file must be checked in Version Control Systems, as it is
4 | # integral to the build system of your project.
5 |
6 | # This file is only used by the Ant script.
7 |
8 | # You can use this to override default values such as
9 | # 'source.dir' for the location of your java source folder and
10 | # 'out.dir' for the location of your output folder.
11 |
12 | # You can also use it define how the release builds are signed by declaring
13 | # the following properties:
14 | # 'key.store' for the location of your keystore and
15 | # 'key.alias' for the name of the key to use.
16 | # The password will be asked during the build when you use the 'release' target.
17 |
18 |
--------------------------------------------------------------------------------
/res/drawable/ic_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
6 |
7 |
8 |
9 |
10 |
11 | -
12 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/res/menu/main_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/res/layout/programme_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
13 |
14 |
25 |
26 |
--------------------------------------------------------------------------------
/res/layout/recording_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
13 |
14 |
25 |
26 |
--------------------------------------------------------------------------------
/res/layout/recording_list_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
13 |
14 |
25 |
26 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/intent/SearchIMDbIntent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this template, choose Tools | Templates
3 | * and open the template in the editor.
4 | */
5 | package org.tvheadend.tvhguide.intent;
6 |
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.content.pm.PackageManager;
10 | import android.net.Uri;
11 | import java.net.URLEncoder;
12 |
13 | /**
14 | *
15 | * @author john-tornblom
16 | */
17 | public class SearchIMDbIntent extends Intent {
18 |
19 | public SearchIMDbIntent(Context ctx, String query) {
20 | super(Intent.ACTION_VIEW);
21 |
22 | setData(Uri.parse("imdb:///find?s=tt&q=" + URLEncoder.encode(query)));
23 |
24 | PackageManager packageManager = ctx.getPackageManager();
25 | if (packageManager.queryIntentActivities(this, PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
26 | setData(Uri.parse("http://akas.imdb.org/find?s=tt&q=" + URLEncoder.encode(query)));
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/htsp/HTSListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.htsp;
20 |
21 | /**
22 | *
23 | * @author john-tornblom
24 | */
25 | public interface HTSListener {
26 |
27 | public void onMessage(String action, Object obj);
28 | }
29 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/htsp/HTSResponseHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.htsp;
20 |
21 | /**
22 | *
23 | * @author john-tornblom
24 | */
25 | public interface HTSResponseHandler {
26 |
27 | public void handleResponse(HTSMessage response);
28 | }
29 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/model/SeriesInfo.java:
--------------------------------------------------------------------------------
1 | package org.tvheadend.tvhguide.model;
2 |
3 | public class SeriesInfo {
4 | public int seasonNumber;
5 | public int seasonCount;
6 |
7 | public int episodeNumber;
8 | public int episodeCount;
9 |
10 | public int partNumber;
11 | public int partCount;
12 |
13 | public String onScreen;
14 |
15 | public String toString() {
16 | if (onScreen != null && onScreen.length() > 0)
17 | return onScreen;
18 |
19 | String s = "";
20 |
21 | if (seasonNumber > 0) {
22 | if (s.length() > 0)
23 | s += ", ";
24 | s += String.format("season %02d", seasonNumber);
25 | }
26 | if (episodeNumber > 0) {
27 | if (s.length() > 0)
28 | s += ", ";
29 | s += String.format("episode %02d", episodeNumber);
30 | }
31 | if (partNumber > 0) {
32 | if (s.length() > 0)
33 | s += ", ";
34 | s += String.format("part %d", partNumber);
35 | }
36 |
37 | if(s.length() > 0) {
38 | s = s.substring(0,1).toUpperCase() + s.substring(1);
39 | }
40 |
41 | return s;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/htsp/HTSConnectionListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.htsp;
20 |
21 | /**
22 | *
23 | * @author john-tornblom
24 | */
25 | public interface HTSConnectionListener {
26 |
27 | public void onMessage(HTSMessage response);
28 | public void onError(int errorCode);
29 | public void onError(Exception ex);
30 | }
31 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/model/HttpTicket.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.model;
20 |
21 | /**
22 | *
23 | * @author john-tornblom
24 | */
25 | public class HttpTicket {
26 | public final String path;
27 | public final String ticket;
28 |
29 | public HttpTicket(String path, String ticket) {
30 | this.path = path;
31 | this.ticket = ticket;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/model/Packet.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.model;
20 |
21 | /**
22 | *
23 | * @author john-tornblom
24 | */
25 | public class Packet {
26 |
27 | public Subscription subscription;
28 | public Stream stream;
29 | public int frametype;
30 | public long dts;
31 | public long pts;
32 | public long duration;
33 | public byte[] payload;
34 | }
35 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/model/ChannelTag.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.model;
20 |
21 | import android.graphics.Bitmap;
22 |
23 | /**
24 | *
25 | * @author john-tornblom
26 | */
27 | public class ChannelTag {
28 |
29 | public long id;
30 | public String name;
31 | public String icon;
32 | public Bitmap iconBitmap;
33 |
34 | @Override
35 | public String toString() {
36 | return name;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/model/Subscription.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.model;
20 |
21 | import java.util.ArrayList;
22 | import java.util.List;
23 |
24 | /**
25 | *
26 | * @author john-tornblom
27 | */
28 | public class Subscription {
29 |
30 | public long id;
31 | public String status;
32 | public List streams = new ArrayList();
33 |
34 | public long packetCount;
35 | public long queSize;
36 | public long delay;
37 | public long droppedBFrames;
38 | public long droppedIFrames;
39 | public long droppedPFrames;
40 | }
41 |
--------------------------------------------------------------------------------
/proguard.cfg:
--------------------------------------------------------------------------------
1 | -optimizationpasses 5
2 | -dontusemixedcaseclassnames
3 | -dontskipnonpubliclibraryclasses
4 | -dontpreverify
5 | -verbose
6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
7 |
8 | -keep public class * extends android.app.Activity
9 | -keep public class * extends android.app.Application
10 | -keep public class * extends android.app.Service
11 | -keep public class * extends android.content.BroadcastReceiver
12 | -keep public class * extends android.content.ContentProvider
13 | -keep public class * extends android.app.backup.BackupAgentHelper
14 | -keep public class * extends android.preference.Preference
15 | -keep public class com.android.vending.licensing.ILicensingService
16 |
17 | -keepclasseswithmembernames class * {
18 | native ;
19 | }
20 |
21 | -keepclasseswithmembers class * {
22 | public (android.content.Context, android.util.AttributeSet);
23 | }
24 |
25 | -keepclasseswithmembers class * {
26 | public (android.content.Context, android.util.AttributeSet, int);
27 | }
28 |
29 | -keepclassmembers class * extends android.app.Activity {
30 | public void *(android.view.View);
31 | }
32 |
33 | -keepclassmembers enum * {
34 | public static **[] values();
35 | public static ** valueOf(java.lang.String);
36 | }
37 |
38 | -keep class * implements android.os.Parcelable {
39 | public static final android.os.Parcelable$Creator *;
40 | }
41 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/model/Stream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.model;
20 |
21 | /**
22 | *
23 | * @author john-tornblom
24 | */
25 | public class Stream {
26 |
27 | public static final String STREAM_TYPE_AC3 = "AC3";
28 | public static final String STREAM_TYPE_MPEG2AUDIO = "MPEG2AUDIO";
29 | public static final String STREAM_TYPE_MPEG2VIDEO = "MPEG2VIDEO";
30 | public static final String STREAM_TYPE_MPEG4VIDEO = "MPEG4VIDEO";
31 | public static final String STREAM_TYPE_H264 = "H264";
32 | public static final String STREAM_TYPE_VP8 = "VP8";
33 | public static final String STREAM_TYPE_AAC = "AAC";
34 | public int index;
35 | public String type;
36 | public String language;
37 | public int height;
38 | public int width;
39 | }
40 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/intent/SearchEPGIntent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.intent;
20 |
21 | import android.app.SearchManager;
22 | import android.content.Context;
23 | import android.content.Intent;
24 |
25 | import org.tvheadend.tvhguide.SearchResultActivity;
26 | import org.tvheadend.tvhguide.model.Channel;
27 |
28 | /**
29 | *
30 | * @author john-tornblom
31 | */
32 | public class SearchEPGIntent extends Intent {
33 |
34 | public SearchEPGIntent(Context ctx, String query) {
35 | super(ctx, SearchResultActivity.class);
36 | setAction(Intent.ACTION_SEARCH);
37 | putExtra(SearchManager.QUERY, query);
38 | }
39 |
40 | public SearchEPGIntent(Context ctx, Channel ch, String query) {
41 | this(ctx, query);
42 | putExtra("channelId", ch.id);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | TVHGuide
4 |
5 | Hilfe
6 | Einstellungen
7 | Aktualisieren
8 | Tags
9 | Aufnahmen
10 |
11 | Hostname
12 | Hostnamen des Servers eingeben
13 |
14 | Port
15 | Port der Servers eingeben
16 |
17 | Benutzername
18 | Benutzernamen eingeben
19 |
20 | Passwort
21 | Passwort eingeben
22 |
23 | Kanal-Logos
24 | Kanal-Logos anzeigen?
25 |
26 | Zugriff verweigert
27 | Ungültige Antwort des Servers
28 | Konnte nicht mit dem Server verbinden
29 | Verbindung zum Server verloren
30 |
31 | Lade...
32 | Bitte warten Sie einen Moment...
33 |
34 | Alle Kanäle
35 |
36 | Geplant
37 | Aufnahme
38 | Fertig
39 | Verpasst
40 | Ungültig
41 |
--------------------------------------------------------------------------------
/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - 576
5 | - 480
6 | - 384
7 | - 288
8 | - 192
9 |
10 |
11 | - 576p
12 | - 480p
13 | - 384p
14 | - 288p
15 | - 192p
16 |
17 |
18 |
19 | - AAC
20 | - MPEG2AUDIO
21 |
22 |
23 | - Advanced Audio Codec (AAC)
24 | - MPEG-2 Audio Layer II (MP2)
25 |
26 |
27 |
28 | - H264
29 | - MPEG4VIDEO
30 | - VP8
31 |
32 |
33 | - MPEG-4 AVC (H.264)
34 | - MPEG-4 Part 2
35 | - VP8
36 |
37 |
38 |
39 | - PASS
40 | - NONE
41 |
42 |
43 | - Pass-through
44 | - None
45 |
46 |
47 |
48 | - matroska
49 | - mpegts
50 | - mpegps
51 | - pass
52 |
53 |
54 |
55 | - Matroska
56 | - MPEG-TS
57 | - MPEG-PS
58 | - Pass-through
59 |
60 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/model/Programme.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.model;
20 |
21 | import java.util.Date;
22 |
23 | /**
24 | *
25 | * @author john-tornblom
26 | */
27 | public class Programme implements Comparable {
28 |
29 | public long id;
30 | public long nextId;
31 | public int contentType;
32 | public Date start;
33 | public Date stop;
34 | public String title;
35 | public String description;
36 | public String summary;
37 | public SeriesInfo seriesInfo;
38 | public int starRating;
39 | public Channel channel;
40 | public Recording recording;
41 |
42 | public int compareTo(Programme that) {
43 | return this.start.compareTo(that.start);
44 | }
45 |
46 | public boolean isRecording() {
47 | return recording != null && "recording".equals(recording.state);
48 | }
49 |
50 | public boolean isScheduled() {
51 | return recording != null && "scheduled".equals(recording.state);
52 | }
53 |
54 | @Override
55 | public boolean equals(Object o) {
56 | if (o instanceof Programme) {
57 | return ((Programme) o).id == id;
58 | }
59 |
60 | return false;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/res/layout/recording_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
21 |
22 |
26 |
27 |
28 |
29 |
35 |
36 |
40 |
41 |
46 |
47 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/model/Recording.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.model;
20 |
21 | import java.util.Date;
22 |
23 | /**
24 | *
25 | * @author john-tornblom
26 | */
27 | public class Recording implements Comparable {
28 |
29 | public long id;
30 | public Date start;
31 | public Date stop;
32 | public String title;
33 | public String summary;
34 | public String description;
35 | public Channel channel;
36 | public String state;
37 | public String error;
38 |
39 | @Override
40 | public int compareTo(Recording that) {
41 | if (this.state() == 1 && that.state() == 1) {
42 | return this.start.compareTo(that.start);
43 | } else {
44 | return that.start.compareTo(this.start);
45 | }
46 | }
47 |
48 | public boolean isRecording() {
49 | return state() == 0;
50 | }
51 |
52 | public boolean isScheduled() {
53 | return state() == 1;
54 | }
55 |
56 | private int state() {
57 | if ("recording".equals(state)) {
58 | return 0;
59 | } else if ("scheduled".equals(state)) {
60 | return 1;
61 | }
62 | return 2;
63 | }
64 |
65 | @Override
66 | public boolean equals(Object o) {
67 | if (o instanceof Recording) {
68 | return ((Recording) o).id == id;
69 | }
70 |
71 | return false;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/model/Channel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.model;
20 |
21 | import android.graphics.Bitmap;
22 | import java.util.Collections;
23 | import java.util.List;
24 | import java.util.Set;
25 | import java.util.TreeSet;
26 |
27 | /**
28 | *
29 | * @author john-tornblom
30 | */
31 | public class Channel implements Comparable {
32 |
33 | public long id;
34 | public String name;
35 | public String icon;
36 | public int number;
37 | public Set epg = Collections.synchronizedSortedSet(new TreeSet());
38 | public Set recordings = Collections.synchronizedSortedSet(new TreeSet());
39 | public List tags;
40 | public Bitmap iconBitmap;
41 | public boolean isTransmitting;
42 |
43 | public int compareTo(Channel that) {
44 | return this.number - that.number;
45 | }
46 |
47 | public boolean hasTag(long id) {
48 | if (id == 0) {
49 | return true;
50 | }
51 |
52 | for (Integer i : tags) {
53 | if (i == id) {
54 | return true;
55 | }
56 | }
57 | return false;
58 | }
59 |
60 | public boolean isRecording() {
61 | for (Recording rec : recordings) {
62 | if ("recording".equals(rec.state)) {
63 | return true;
64 | }
65 | }
66 | return false;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/res/layout/programme_list_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
16 |
22 |
32 |
33 |
34 |
48 |
49 |
58 |
--------------------------------------------------------------------------------
/res/layout/search_result_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
16 |
22 |
32 |
33 |
34 |
48 |
49 |
58 |
--------------------------------------------------------------------------------
/res/layout/player_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
22 |
23 |
29 |
30 |
36 |
37 |
38 |
44 |
49 |
50 |
51 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/TVHVideoView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide;
20 |
21 | import android.content.Context;
22 | import android.util.AttributeSet;
23 | import android.widget.VideoView;
24 |
25 | /**
26 | *
27 | * @author john-tornblom
28 | */
29 | public class TVHVideoView extends VideoView {
30 |
31 | private int videoWidth;
32 | private int videoHeight;
33 |
34 | public TVHVideoView(Context context) {
35 | super(context);
36 | }
37 |
38 | public TVHVideoView(Context context, AttributeSet attrs) {
39 | super(context, attrs);
40 | }
41 |
42 | public TVHVideoView(Context context, AttributeSet attrs, int defStyle) {
43 | super(context, attrs, defStyle);
44 | }
45 |
46 | public void setAspectRatio(int num, int den) {
47 | videoHeight = getHeight();
48 | videoWidth = (videoHeight * num) / den;
49 |
50 | requestLayout();
51 | invalidate();
52 | }
53 |
54 | @Override
55 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
56 |
57 | int width = getDefaultSize(videoWidth, widthMeasureSpec);
58 | int height = getDefaultSize(videoHeight, heightMeasureSpec);
59 |
60 | if (videoWidth > 0 && videoHeight > 0) {
61 | if (videoWidth * height > width * videoHeight) {
62 | height = width * videoHeight / videoWidth;
63 | } else if (videoWidth * height < width * videoHeight) {
64 | width = height * videoWidth / videoHeight;
65 | }
66 | }
67 |
68 | setMeasuredDimension(width, height);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/res/layout/programme_list_widget.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
20 |
21 |
22 |
28 |
29 |
30 |
31 |
37 |
38 |
44 |
45 |
46 |
51 |
52 |
53 |
59 |
60 |
61 |
62 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/res/layout/channel_list_title.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
16 |
22 |
32 |
33 |
34 |
48 |
49 |
58 |
67 |
--------------------------------------------------------------------------------
/res/layout/search_result_widget.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
18 |
19 |
23 |
27 |
33 |
34 |
40 |
41 |
42 |
47 |
50 |
55 |
56 |
62 |
63 |
64 |
67 |
72 |
73 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/res/layout/channel_list_widget.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
20 |
21 |
25 |
26 |
31 |
32 |
37 |
38 |
42 |
43 |
48 |
49 |
55 |
56 |
57 |
60 |
61 |
67 |
68 |
69 |
72 |
73 |
78 |
79 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide;
20 |
21 | import android.content.Intent;
22 | import android.content.SharedPreferences;
23 | import android.os.Bundle;
24 | import android.preference.PreferenceActivity;
25 | import android.preference.PreferenceManager;
26 | import android.util.Log;
27 | import android.view.Window;
28 | import org.tvheadend.tvhguide.R;
29 | import org.tvheadend.tvhguide.htsp.HTSService;
30 |
31 | /**
32 | *
33 | * @author john-tornblom
34 | */
35 | public class SettingsActivity extends PreferenceActivity {
36 |
37 | private int oldPort;
38 | private String oldHostname;
39 | private String oldUser;
40 | private String oldPw;
41 |
42 | @Override
43 | public void onCreate(Bundle savedInstanceState) {
44 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
45 | Boolean theme = prefs.getBoolean("lightThemePref", false);
46 | setTheme(theme ? android.R.style.Theme_Light : android.R.style.Theme);
47 |
48 | requestWindowFeature(Window.FEATURE_LEFT_ICON);
49 | super.onCreate(savedInstanceState);
50 |
51 | addPreferencesFromResource(R.xml.preferences);
52 | setTitle(getString(R.string.app_name) + " - " + getString(R.string.menu_settings));
53 | setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.logo_72);
54 | }
55 |
56 | @Override
57 | protected void onStart() {
58 | super.onStart();
59 |
60 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
61 | oldHostname = prefs.getString("serverHostPref", "");
62 | oldPort = Integer.parseInt(prefs.getString("serverPortPref", ""));
63 | oldUser = prefs.getString("usernamePref", "");
64 | oldPw = prefs.getString("passwordPref", "");
65 | }
66 |
67 | @Override
68 | protected void onPause() {
69 | super.onPause();
70 |
71 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
72 | boolean reconnect = false;
73 | reconnect |= !oldHostname.equals(prefs.getString("serverHostPref", ""));
74 | reconnect |= oldPort != Integer.parseInt(prefs.getString("serverPortPref", ""));
75 | reconnect |= !oldUser.equals(prefs.getString("usernamePref", ""));
76 | reconnect |= !oldPw.equals(prefs.getString("passwordPref", ""));
77 |
78 | if (reconnect) {
79 | Log.d("SettingsActivity", "Connectivity settings chaned, forcing a reconnect");
80 | Intent intent = new Intent(SettingsActivity.this, HTSService.class);
81 | intent.setAction(HTSService.ACTION_CONNECT);
82 | intent.putExtra("hostname", prefs.getString("serverHostPref", ""));
83 | intent.putExtra("port", Integer.parseInt(prefs.getString("serverPortPref", "")));
84 | intent.putExtra("username", prefs.getString("usernamePref", ""));
85 | intent.putExtra("password", prefs.getString("passwordPref", ""));
86 | intent.putExtra("force", true);
87 | startService(intent);
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/res/values-cs/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | TVHGuide
4 |
5 | Nápověda
6 | Nastavení
7 | Obnovit
8 | Štítky
9 | Nahrávání
10 |
11 | Nahrávky
12 | Zrušit nahrávání
13 | Vymazat nahrávání
14 |
15 | Hostname
16 | Nazev serveru (Hostname)
17 |
18 | Port
19 | Port serveru
20 |
21 | Uživatelské jméno
22 | Vložte jméno
23 |
24 | Heslo
25 | Vložte heslo
26 |
27 | Ikony kanálů
28 | Zobrazit ikony kanálů?
29 |
30 | Rozlišení
31 | Preferované maximální rozlišení videa
32 |
33 | Audio kodek
34 | Preferovaný audio kodek
35 |
36 | Video kodek
37 | Preferovaný video kodek
38 |
39 | Kodek pro titulky
40 | Preferovaný kodek pro titulky
41 |
42 | Media kontejner
43 | Preferovaný proudový kontejner
44 |
45 | Přehrávání
46 |
47 | HTTP port
48 | Vložte HTTP port Serveru
49 |
50 | Překódovat proud
51 | Požadovat překódování pro živý přenos
52 |
53 | Preferovaný externí přehrávač
54 | Použije se pro živé streamy (BSPlayer seems OK)
55 |
56 | Přístup odmítnut
57 | Neplatná odpověď serveru
58 | Nelze se připojit na server
59 | Spojení se serverem ztraceno
60 |
61 | Načítám…
62 | Prosím čekejte…
63 |
64 | Všechny kanály
65 | Více
66 |
67 | - Filmy / Divadlo
68 | - Zprávy / Aktuality
69 | - Seriály / Hry
70 | - Sport
71 | - Dětské / Pro mladistvé
72 | - Hudba
73 | - Umění / Kultura
74 | - Společnost / Politika / Ekonomie
75 | - Vzdělání / Věda / Naučné
76 | - Hobby
77 |
78 |
79 | Plánované
80 | Nahrávání
81 | Kompletní
82 | Ztracené
83 | Neplatné
84 |
85 | Přehrát
86 | Žádný přenos
87 |
88 | Hledat v EPG
89 | dnes
90 |
--------------------------------------------------------------------------------
/res/values-sv/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | TVHGuide
4 |
5 | Hjälp
6 | Inställningar
7 | Uppdatera
8 | Taggar
9 | Inspelningar
10 |
11 | Spela in
12 | Ta bort inspelning
13 | Avbryt inspelning
14 |
15 | Värdnamn
16 | Skriv in värdnamn
17 |
18 | Port
19 | Skriv in portnummer
20 |
21 | Användarnamn
22 | Skriv in användarnamn
23 |
24 | Lösenord
25 | Skriv in lösenord
26 |
27 | Användargränssnitt
28 |
29 | Ljust tema
30 | Använd ett ljust tema
31 |
32 | Kanallogotyper
33 | Visa kanalers logotyper?
34 |
35 | Upplösning
36 | Maximal upplösning vid omkodning
37 |
38 | Ljud
39 | Kodek att använda vid omkodning av ljud
40 |
41 | Video
42 | Kodek att använda vid omkodning av video
43 |
44 | Undertexter
45 | Kodek att använda vid omkodning av undertexter
46 |
47 | Media container
48 | Media container att föredra
49 |
50 | Uppspelning
51 |
52 | HTTP port
53 | Skriv in HTTP portnummer
54 |
55 | Koda om ström
56 | Begär omkodning av direktsänd media
57 |
58 | Föredra extern spelare
59 | Använd en extern spelare för direktsänd media (BSPlayer verkar OK)
60 |
61 | Åtkomst nekad
62 | Felaktigt svar från servern
63 | Kan inte ansluta till servern
64 | Tappade anslutningen till servern
65 |
66 | Laddar…
67 | Var god vänta några sekunder…
68 |
69 | Alla kanaler
70 | Hämta flera
71 | Säsong
72 | Avsnitt
73 | Del
74 | Sänds
75 | Omdömme
76 | Typ
77 | Del
78 |
79 | Schemalagd
80 | Spelar in
81 | Avslutad
82 | Missad
83 | Ogiltig
84 |
85 | Spela
86 | Sändningsuppehåll
87 |
88 | Sök i programtablån
89 | idag
90 |
--------------------------------------------------------------------------------
/res/layout/recording_list_widget.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
18 |
19 |
20 |
24 |
28 |
34 |
35 |
41 |
42 |
43 |
48 |
51 |
54 |
59 |
60 |
66 |
67 |
68 |
74 |
75 |
76 |
79 |
84 |
85 |
91 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/htsp/HTSFileInputStream.java:
--------------------------------------------------------------------------------
1 | package org.tvheadend.tvhguide.htsp;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 |
6 | import android.util.Log;
7 |
8 | public class HTSFileInputStream extends InputStream {
9 | private HTSConnection connection;
10 | private String path;
11 |
12 | private long fileId;
13 | private long fileSize;
14 |
15 | private byte[] buf;
16 | private int bufPos;
17 | private long offset;
18 |
19 | public HTSFileInputStream(HTSConnection conn, String path)
20 | throws IOException {
21 | this.connection = conn;
22 | this.path = path;
23 |
24 | this.fileId = -1;
25 | this.fileSize = -1;
26 |
27 | this.reset();
28 | this.open();
29 | }
30 |
31 | public int available() {
32 | return this.buf.length;
33 | }
34 |
35 | public boolean markSupported() {
36 | return false;
37 | }
38 |
39 | public void reset() {
40 | buf = new byte[0];
41 | bufPos = 0;
42 | offset = 0;
43 | }
44 |
45 | class FileOpenResponse implements HTSResponseHandler {
46 | int id;
47 | long size;
48 | long mtime;
49 |
50 | @Override
51 | public void handleResponse(HTSMessage response) {
52 | id = response.getInt("id", 0);
53 | size = response.getLong("size", 0);
54 | mtime = response.getLong("mtime", 0);
55 | notifyAll();
56 | }
57 | };
58 |
59 | class FileReadResponse implements HTSResponseHandler {
60 | byte[] data;
61 |
62 | @Override
63 | public void handleResponse(HTSMessage response) {
64 | data = response.getByteArray("data");
65 | notifyAll();
66 | }
67 | };
68 |
69 | class FileCloseResponse implements HTSResponseHandler {
70 | int id;
71 |
72 | @Override
73 | public void handleResponse(HTSMessage response) {
74 | notifyAll();
75 | }
76 | };
77 |
78 | private void open() throws IOException {
79 | HTSMessage request = new HTSMessage();
80 | FileOpenResponse response = new FileOpenResponse();
81 |
82 | request.setMethod("fileOpen");
83 | request.putField("file", path);
84 |
85 | synchronized (response) {
86 | try {
87 | connection.sendMessage(request, response);
88 | response.wait();
89 | fileId = response.id;
90 | fileSize = response.size;
91 | } catch (Throwable e) {
92 | Log.e("TVHGuide", "Timeout waiting for fileOpen", e);
93 | }
94 | }
95 |
96 | if (fileId < 0) {
97 | throw new IOException("Failed to open remote file");
98 | } else if (fileId == 0) {
99 | throw new IOException("Remote file is missing");
100 | }
101 | }
102 |
103 | public void close() {
104 | HTSMessage request = new HTSMessage();
105 | FileCloseResponse response = new FileCloseResponse();
106 |
107 | request.setMethod("fileClose");
108 | request.putField("id", fileId);
109 |
110 | synchronized (response) {
111 | try {
112 | connection.sendMessage(request, response);
113 | response.wait();
114 | fileId = -1;
115 | fileSize = -1;
116 | } catch (Throwable e) {
117 | Log.e("TVHGuide", "Timeout waiting for fileClose", e);
118 | }
119 | }
120 | }
121 |
122 | public int read(byte[] outBuf, int outOffset, int outLength) {
123 | fillBuffer();
124 |
125 | int ret = Math.min(buf.length - bufPos, outLength - outOffset);
126 | if(ret > 0) {
127 | System.arraycopy(buf, bufPos, outBuf, outOffset, ret);
128 | bufPos += ret;
129 | return ret;
130 | }
131 |
132 | return -1;
133 | }
134 |
135 | @Override
136 | public int read() throws IOException {
137 | fillBuffer();
138 |
139 | if (bufPos < buf.length) {
140 | return buf[bufPos++] & 0xff;
141 | }
142 |
143 | return -1;
144 | }
145 |
146 | private void fillBuffer() {
147 | if(bufPos < buf.length) {
148 | return;
149 | }
150 |
151 | HTSMessage request = new HTSMessage();
152 | FileReadResponse response = new FileReadResponse();
153 |
154 | request.setMethod("fileRead");
155 | request.putField("id", fileId);
156 | request.putField("size", Math.min(fileSize, 1024 * 1024 * 8));
157 | request.putField("offset", offset);
158 |
159 | synchronized (response) {
160 | try {
161 | connection.sendMessage(request, response);
162 | response.wait();
163 |
164 | offset += buf.length;
165 | buf = response.data;
166 | bufPos = 0;
167 | } catch (Throwable e) {
168 | Log.e("TVHGuide", "Timeout waiting for fileRead", e);
169 | }
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/ChannelListViewWrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide;
20 |
21 | import android.content.SharedPreferences;
22 | import android.graphics.drawable.BitmapDrawable;
23 | import android.graphics.drawable.ClipDrawable;
24 | import android.preference.PreferenceManager;
25 | import android.text.format.DateFormat;
26 | import android.view.Gravity;
27 | import android.view.View;
28 | import android.widget.ImageView;
29 | import android.widget.TextView;
30 | import java.util.Date;
31 | import java.util.Iterator;
32 | import org.tvheadend.tvhguide.R;
33 | import org.tvheadend.tvhguide.model.Channel;
34 | import org.tvheadend.tvhguide.model.Programme;
35 |
36 | /**
37 | *
38 | * @author john-tornblom
39 | */
40 | public class ChannelListViewWrapper {
41 |
42 | private TextView name;
43 | private TextView nowTitle;
44 | private TextView nowTime;
45 | private TextView nextTitle;
46 | private TextView nextTime;
47 | private ImageView icon;
48 | private ImageView nowProgressImage;
49 | private ClipDrawable nowProgress;
50 |
51 | public ChannelListViewWrapper(View base) {
52 | name = (TextView) base.findViewById(R.id.ch_name);
53 | nowTitle = (TextView) base.findViewById(R.id.ch_now_title);
54 |
55 | nowProgressImage = (ImageView) base.findViewById(R.id.ch_elapsedtime);
56 | nowProgress = new ClipDrawable(nowProgressImage.getDrawable(), Gravity.LEFT, ClipDrawable.HORIZONTAL);
57 | nowProgressImage.setBackgroundDrawable(nowProgress);
58 |
59 | nowTime = (TextView) base.findViewById(R.id.ch_now_time);
60 | nextTitle = (TextView) base.findViewById(R.id.ch_next_title);
61 | nextTime = (TextView) base.findViewById(R.id.ch_next_time);
62 | icon = (ImageView) base.findViewById(R.id.ch_icon);
63 | }
64 |
65 | public void repaint(Channel channel) {
66 | nowTime.setText("");
67 | nowTitle.setText("");
68 | nextTime.setText("");
69 | nextTitle.setText("");
70 | nowProgress.setLevel(0);
71 |
72 | name.setText(channel.name);
73 | name.invalidate();
74 |
75 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(icon.getContext());
76 | Boolean showIcons = prefs.getBoolean("showIconPref", false);
77 | icon.setVisibility(showIcons ? ImageView.VISIBLE : ImageView.GONE);
78 | icon.setBackgroundDrawable(new BitmapDrawable(channel.iconBitmap));
79 |
80 | if (channel.isRecording()) {
81 | icon.setImageResource(R.drawable.ic_rec_small);
82 | } else {
83 | icon.setImageDrawable(null);
84 | }
85 | icon.invalidate();
86 |
87 | Iterator it = channel.epg.iterator();
88 | if (!channel.isTransmitting && it.hasNext()) {
89 | nowTitle.setText(R.string.ch_no_transmission);
90 | } else if (it.hasNext()) {
91 | Programme p = it.next();
92 | nowTime.setText(
93 | DateFormat.getTimeFormat(nowTime.getContext()).format(p.start)
94 | + " - "
95 | + DateFormat.getTimeFormat(nowTime.getContext()).format(p.stop));
96 |
97 | double duration = (p.stop.getTime() - p.start.getTime());
98 | double elapsed = new Date().getTime() - p.start.getTime();
99 | double percent = elapsed / duration;
100 |
101 | nowProgressImage.setVisibility(ImageView.VISIBLE);
102 | nowProgress.setLevel((int) Math.floor(percent * 10000));
103 | nowTitle.setText(p.title);
104 | } else {
105 | nowProgressImage.setVisibility(ImageView.GONE);
106 | }
107 | nowProgressImage.invalidate();
108 | nowTime.invalidate();
109 | nowTitle.invalidate();
110 |
111 | if (it.hasNext()) {
112 | Programme p = it.next();
113 | nextTime.setText(
114 | DateFormat.getTimeFormat(nextTime.getContext()).format(p.start)
115 | + " - "
116 | + DateFormat.getTimeFormat(nextTime.getContext()).format(p.stop));
117 |
118 | nextTitle.setText(p.title);
119 | }
120 | nextTime.invalidate();
121 | nextTitle.invalidate();
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/ExternalPlaybackActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide;
20 |
21 | import android.app.Activity;
22 | import android.content.Intent;
23 | import android.content.SharedPreferences;
24 | import android.net.Uri;
25 | import android.os.Bundle;
26 | import android.preference.PreferenceManager;
27 | import android.util.Log;
28 |
29 | import org.tvheadend.tvhguide.htsp.HTSListener;
30 | import org.tvheadend.tvhguide.htsp.HTSService;
31 | import org.tvheadend.tvhguide.model.HttpTicket;
32 | import org.tvheadend.tvhguide.model.Stream;
33 |
34 | /**
35 | *
36 | * @author john-tornblom
37 | */
38 | public class ExternalPlaybackActivity extends Activity implements HTSListener {
39 |
40 | @Override
41 | protected void onCreate(Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 |
44 | Intent intent = new Intent(ExternalPlaybackActivity.this, HTSService.class);
45 | intent.setAction(HTSService.ACTION_GET_TICKET);
46 |
47 | intent.putExtras(getIntent().getExtras());
48 | this.startService(intent);
49 | }
50 |
51 | @Override
52 | protected void onResume() {
53 | super.onResume();
54 |
55 | TVHGuideApplication app = (TVHGuideApplication) getApplication();
56 | app.addListener(this);
57 | }
58 |
59 | @Override
60 | protected void onPause() {
61 | super.onPause();
62 |
63 | TVHGuideApplication app = (TVHGuideApplication) getApplication();
64 | app.removeListener(this);
65 | }
66 |
67 | private void startPlayback(String path, String ticket) {
68 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
69 |
70 | String host = prefs.getString("serverHostPref", "localhost");
71 | Integer port = Integer.parseInt(prefs.getString("httpPortPref", "9981"));
72 |
73 | Integer resolution = Integer.parseInt(prefs.getString("resolutionPref", "288"));
74 | Boolean transcode = false; //prefs.getBoolean("transcodePref", true);
75 | String acodec = prefs.getString("acodecPref", Stream.STREAM_TYPE_AAC);
76 | String vcodec = prefs.getString("vcodecPref", Stream.STREAM_TYPE_MPEG4VIDEO);
77 | String scodec = prefs.getString("scodecPref", "PASS");
78 | String container = prefs.getString("containerPref", "matroska");
79 | String mime = "application/octet-stream";
80 |
81 | if ("mpegps".equals(container)) {
82 | mime = "video/mp2p";
83 | } else if ("mpegts".equals(container)) {
84 | mime = "video/mp4";
85 | } else if ("matroska".equals(container)) {
86 | mime = "video/x-matroska";
87 | } else if ("pass".equals(container)) {
88 | mime = "video/mp2t"; //assume mpegts
89 | }
90 |
91 | String url = "http://" + host + ":" + port + path;
92 | url += "?ticket=" + ticket;
93 | url += "&mux=" + container;
94 | if (transcode) {
95 | url += "&transcode=1";
96 | url += "&resolution=" + resolution;
97 | url += "&acodec=" + acodec;
98 | url += "&vcodec=" + vcodec;
99 | url += "&scodec=" + scodec;
100 | }
101 |
102 | final Intent playbackIntent = new Intent(Intent.ACTION_VIEW);
103 | playbackIntent.setDataAndType(Uri.parse(url), mime);
104 |
105 |
106 | this.runOnUiThread(new Runnable() {
107 |
108 | public void run() {
109 | try {
110 | startActivity(playbackIntent);
111 | } catch (Throwable t) {
112 | Log.e("TVHGuide", "Can't execute external media player", t);
113 | try {
114 | Intent installIntent = new Intent(Intent.ACTION_VIEW);
115 | installIntent.setData(Uri.parse("market://search?q=free%20video%20player&c=apps"));
116 | startActivity(installIntent);
117 | } catch (Throwable t2) {
118 | Log.e("TVHGuide", "Can't query market", t2);
119 | }
120 | } finally {
121 | finish();
122 | }
123 | }
124 | });
125 | }
126 |
127 | public void onMessage(String action, final Object obj) {
128 | if (action.equals(TVHGuideApplication.ACTION_TICKET_ADD)) {
129 | HttpTicket t = (HttpTicket) obj;
130 | startPlayback(t.path, t.ticket);
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
19 |
20 |
26 |
27 |
34 |
35 |
36 |
38 |
39 |
45 |
46 |
52 |
53 |
54 |
56 |
64 |
73 |
74 |
125 |
126 |
127 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/RecordingActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide;
20 |
21 | import android.app.Activity;
22 | import android.app.AlertDialog;
23 | import android.content.DialogInterface;
24 | import android.content.Intent;
25 | import android.content.SharedPreferences;
26 | import android.os.Bundle;
27 | import android.preference.PreferenceManager;
28 | import android.text.format.DateFormat;
29 | import android.view.Menu;
30 | import android.view.MenuItem;
31 | import android.view.Window;
32 | import android.widget.ImageView;
33 | import android.widget.TextView;
34 |
35 | import org.tvheadend.tvhguide.R;
36 | import org.tvheadend.tvhguide.htsp.HTSService;
37 | import org.tvheadend.tvhguide.intent.SearchEPGIntent;
38 | import org.tvheadend.tvhguide.intent.SearchIMDbIntent;
39 | import org.tvheadend.tvhguide.model.Recording;
40 |
41 | /**
42 | *
43 | * @author john-tornblom
44 | */
45 | public class RecordingActivity extends Activity {
46 |
47 | Recording rec;
48 |
49 | @Override
50 | public void onCreate(Bundle savedInstanceState) {
51 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
52 | Boolean theme = prefs.getBoolean("lightThemePref", false);
53 | setTheme(theme ? R.style.CustomTheme_Light : R.style.CustomTheme);
54 |
55 | super.onCreate(savedInstanceState);
56 |
57 | TVHGuideApplication app = (TVHGuideApplication) getApplication();
58 | rec = app.getRecording(getIntent().getLongExtra("id", 0));
59 | if (rec == null) {
60 | finish();
61 | return;
62 | }
63 |
64 | requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
65 |
66 | setContentView(R.layout.recording_layout);
67 |
68 | getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.recording_title);
69 | TextView t = (TextView) findViewById(R.id.ct_title);
70 | t.setText(rec.channel.name);
71 |
72 | if (rec.channel.iconBitmap != null) {
73 | ImageView iv = (ImageView) findViewById(R.id.ct_logo);
74 | iv.setImageBitmap(rec.channel.iconBitmap);
75 | }
76 |
77 | TextView text = (TextView) findViewById(R.id.rec_name);
78 | text.setText(rec.title);
79 |
80 | text = (TextView) findViewById(R.id.rec_summary);
81 | text.setText(rec.summary);
82 | if(rec.summary.length() == 0)
83 | text.setVisibility(TextView.GONE);
84 |
85 | text = (TextView) findViewById(R.id.rec_desc);
86 | text.setText(rec.description);
87 |
88 | text = (TextView) findViewById(R.id.rec_time);
89 | text.setText(
90 | DateFormat.getLongDateFormat(this).format(rec.start)
91 | + " "
92 | + DateFormat.getTimeFormat(this).format(rec.start)
93 | + " - "
94 | + DateFormat.getTimeFormat(this).format(rec.stop));
95 | }
96 |
97 | @Override
98 | public boolean onCreateOptionsMenu(Menu menu) {
99 | MenuItem item = null;
100 |
101 | if (rec.title != null) {
102 | item = menu.add(Menu.NONE, android.R.string.search_go, Menu.NONE, android.R.string.search_go);
103 | item.setIntent(new SearchEPGIntent(this, rec.title));
104 | item.setIcon(android.R.drawable.ic_menu_search);
105 |
106 | item = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, "IMDb");
107 | item.setIntent(new SearchIMDbIntent(this, rec.title));
108 | item.setIcon(android.R.drawable.ic_menu_info_details);
109 | }
110 |
111 | Intent intent = new Intent(this, HTSService.class);
112 |
113 | if (rec.isRecording() || rec.isScheduled()) {
114 | intent.setAction(HTSService.ACTION_DVR_CANCEL);
115 | intent.putExtra("id", rec.id);
116 | item = menu.add(Menu.NONE, R.string.menu_record_cancel, Menu.NONE, R.string.menu_record_cancel);
117 | item.setIntent(intent);
118 | item.setIcon(android.R.drawable.ic_menu_close_clear_cancel);
119 | item.setIntent(intent);
120 | } else {
121 | intent.setAction(HTSService.ACTION_DVR_DELETE);
122 | intent.putExtra("id", rec.id);
123 | item = menu.add(Menu.NONE, R.string.menu_record_remove, Menu.NONE, R.string.menu_record_remove);
124 | item.setIntent(intent);
125 | item.setIcon(android.R.drawable.ic_menu_delete);
126 | item.setIntent(intent);
127 |
128 | intent = new Intent(this, ExternalPlaybackActivity.class);
129 | intent.putExtra("dvrId", rec.id);
130 | item = menu.add(Menu.NONE, R.string.ch_play, Menu.NONE, R.string.ch_play);
131 | item.setIntent(intent);
132 | item.setIcon(android.R.drawable.ic_menu_view);
133 | }
134 |
135 | return true;
136 | }
137 |
138 | @Override
139 | public boolean onPrepareOptionsMenu(Menu menu) {
140 | boolean rebuild = false;
141 | if (rec.isRecording() || rec.isScheduled()) {
142 | rebuild = menu.findItem(R.string.menu_record_cancel) == null;
143 | } else {
144 | rebuild = menu.findItem(R.string.menu_record_remove) == null;
145 | }
146 |
147 | if (rebuild) {
148 | menu.clear();
149 | return onCreateOptionsMenu(menu);
150 | }
151 |
152 | return true;
153 | }
154 |
155 | @Override
156 | public boolean onOptionsItemSelected(final MenuItem item) {
157 | switch (item.getItemId()) {
158 | case R.string.menu_record_remove: {
159 | new AlertDialog.Builder(this)
160 | .setTitle(R.string.menu_record_remove)
161 | .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
162 |
163 | public void onClick(DialogInterface dialog, int which) {
164 | startService(item.getIntent());
165 | }
166 | }).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
167 |
168 | public void onClick(DialogInterface dialog, int which) {
169 | //NOP
170 | }
171 | }).show();
172 | }
173 | case R.string.menu_record_cancel:
174 | startService(item.getIntent());
175 | return true;
176 | default:
177 | return super.onOptionsItemSelected(item);
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/PlaybackActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide;
20 |
21 | import android.app.Activity;
22 | import android.content.Intent;
23 | import android.content.SharedPreferences;
24 | import android.graphics.PixelFormat;
25 | import android.media.MediaPlayer;
26 | import android.media.MediaPlayer.OnErrorListener;
27 | import android.media.MediaPlayer.OnPreparedListener;
28 | import android.net.Uri;
29 | import android.os.Bundle;
30 | import android.preference.PreferenceManager;
31 | import android.text.format.DateFormat;
32 | import android.view.View.OnClickListener;
33 | import android.view.*;
34 | import android.widget.FrameLayout;
35 | import android.widget.LinearLayout;
36 | import android.widget.TextView;
37 | import java.util.Date;
38 | import org.tvheadend.tvhguide.R;
39 | import org.tvheadend.tvhguide.htsp.HTSListener;
40 | import org.tvheadend.tvhguide.htsp.HTSService;
41 | import org.tvheadend.tvhguide.model.Channel;
42 | import org.tvheadend.tvhguide.model.HttpTicket;
43 | import org.tvheadend.tvhguide.model.Stream;
44 |
45 | /**
46 | *
47 | * @author john-tornblom
48 | */
49 | public class PlaybackActivity extends Activity implements HTSListener {
50 |
51 | private TVHVideoView videoView;
52 |
53 | @Override
54 | protected void onCreate(Bundle savedInstanceState) {
55 | super.onCreate(savedInstanceState);
56 |
57 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
58 | if (prefs.getBoolean("externalPref", false)) {
59 | Intent intent = new Intent(this, ExternalPlaybackActivity.class);
60 | intent.putExtras(this.getIntent().getExtras());
61 | startActivity(intent);
62 | finish();
63 | return;
64 | }
65 |
66 | TVHGuideApplication app = (TVHGuideApplication) getApplication();
67 | final Channel channel = app.getChannel(getIntent().getLongExtra("channelId", 0));
68 | if (channel == null) {
69 | finish();
70 | return;
71 | }
72 |
73 | setTitle(channel.name);
74 |
75 | requestWindowFeature(Window.FEATURE_NO_TITLE);
76 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
77 | WindowManager.LayoutParams.FLAG_FULLSCREEN);
78 |
79 | setContentView(R.layout.player_layout);
80 |
81 | getWindow().setFormat(PixelFormat.TRANSLUCENT);
82 |
83 | final TextView clock = (TextView) findViewById(R.id.pl_clock);
84 | final LinearLayout headerOverlay = (LinearLayout) findViewById(R.id.pl_header);
85 | final LinearLayout middleOverlay = (LinearLayout) findViewById(R.id.pl_middle);
86 | final LinearLayout footerOverlay = (LinearLayout) findViewById(R.id.pl_footer);
87 |
88 | videoView = (TVHVideoView) findViewById(R.id.pl_video);
89 | videoView.setOnPreparedListener(new OnPreparedListener() {
90 |
91 | public void onPrepared(MediaPlayer arg0) {
92 | middleOverlay.setVisibility(LinearLayout.GONE);
93 | }
94 | });
95 |
96 | videoView.setOnErrorListener(new OnErrorListener() {
97 |
98 | public boolean onError(MediaPlayer arg0, int arg1, int arg2) {
99 | finish();
100 | return true;
101 | }
102 | });
103 |
104 | LayoutInflater inflater = getLayoutInflater();
105 | View v = inflater.inflate(R.layout.channel_list_widget, null, false);
106 | final ChannelListViewWrapper w = new ChannelListViewWrapper(v);
107 | footerOverlay.addView(v);
108 |
109 | FrameLayout frameLayout = (FrameLayout) findViewById(R.id.pl_frame);
110 | frameLayout.setOnClickListener(new OnClickListener() {
111 |
112 | public void onClick(View arg0) {
113 | if (headerOverlay.getVisibility() == LinearLayout.VISIBLE) {
114 | headerOverlay.setVisibility(LinearLayout.INVISIBLE);
115 | footerOverlay.setVisibility(LinearLayout.INVISIBLE);
116 | } else {
117 | w.repaint(channel);
118 | clock.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.RIGHT);
119 | clock.setText(DateFormat.getTimeFormat(clock.getContext()).format(new Date()));
120 | headerOverlay.setVisibility(LinearLayout.VISIBLE);
121 | footerOverlay.setVisibility(LinearLayout.VISIBLE);
122 | }
123 | }
124 | });
125 |
126 | Intent intent = new Intent(PlaybackActivity.this, HTSService.class);
127 | intent.setAction(HTSService.ACTION_GET_TICKET);
128 | intent.putExtras(getIntent().getExtras());
129 | startService(intent);
130 | }
131 |
132 | @Override
133 | protected void onResume() {
134 | super.onResume();
135 |
136 | TVHGuideApplication app = (TVHGuideApplication) getApplication();
137 | app.addListener(this);
138 | }
139 |
140 | @Override
141 | protected void onPause() {
142 | super.onPause();
143 |
144 | TVHGuideApplication app = (TVHGuideApplication) getApplication();
145 | app.removeListener(this);
146 | }
147 |
148 | private void startPlayback(String path, String ticket) {
149 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
150 |
151 | String host = prefs.getString("serverHostPref", "localhost");
152 | Integer port = Integer.parseInt(prefs.getString("httpPortPref", "9981"));
153 | Integer resolution = Integer.parseInt(prefs.getString("resolutionPref", "288"));
154 | Boolean transcode = prefs.getBoolean("transcodePref", true);
155 | String container = prefs.getString("containerPref", "matroska");
156 | String acodec = prefs.getString("acodecPref", Stream.STREAM_TYPE_AAC);
157 | String vcodec = prefs.getString("vcodecPref", Stream.STREAM_TYPE_H264);
158 | String scodec = prefs.getString("scodecPref", "NONE");
159 |
160 | String url = "http://" + host + ":" + port + path;
161 | url += "?ticket=" + ticket;
162 | url += "&mux=" + container;
163 | if (transcode) {
164 | url += "&transcode=1";
165 | url += "&resolution=" + resolution;
166 | url += "&acodec=" + acodec;
167 | url += "&vcodec=" + vcodec;
168 | url += "&scodec=" + scodec;
169 | }
170 |
171 | videoView.setVideoURI(Uri.parse(url));
172 | videoView.requestFocus();
173 | videoView.start();
174 | videoView.setAspectRatio(16, 9);
175 |
176 | TextView codecInfo = (TextView) findViewById(R.id.pl_codec);
177 |
178 | container = valueToName(R.array.pref_container_list,
179 | R.array.pref_container_list_display, container);
180 |
181 | String c = container;
182 | if (transcode) {
183 | c += " (";
184 | c += acodec + ", ";
185 | c += vcodec + "@" + resolution;
186 | c += ")";
187 | }
188 |
189 | codecInfo.setGravity(Gravity.CENTER_HORIZONTAL);
190 | codecInfo.setText(c);
191 | }
192 |
193 | private String valueToName(int valueRresouce, int nameResource, String val) {
194 |
195 | String[] names = getResources().getStringArray(nameResource);
196 | String[] values = getResources().getStringArray(valueRresouce);
197 |
198 | for (int i = 0; i < values.length; i++) {
199 | if (values[i].equals(val)) {
200 | return names[i];
201 | }
202 | }
203 | return "";
204 | }
205 |
206 | public void onMessage(String action, final Object obj) {
207 | if (action.equals(TVHGuideApplication.ACTION_TICKET_ADD)) {
208 |
209 | this.runOnUiThread(new Runnable() {
210 |
211 | public void run() {
212 | HttpTicket t = (HttpTicket) obj;
213 | startPlayback(t.path, t.ticket);
214 | }
215 | });
216 |
217 | }
218 | }
219 | }
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | TVHGuide
4 |
5 | Help
6 | Settings
7 | Refresh
8 | Tags
9 | Recordings
10 |
11 | Record
12 | Cancel recording
13 | Remove recording
14 |
15 | Hostname
16 | Enter Server Hostname
17 |
18 | Port
19 | Enter Server Port
20 |
21 | Username
22 | Enter Username
23 |
24 | Password
25 | Enter Password
26 |
27 | User interface
28 |
29 | Channel icons
30 | Show channel icons?
31 |
32 | Light theme
33 | Use a light theme
34 |
35 | Resolution
36 | Maximum resolution for the transcoder
37 |
38 | Audio codec
39 | Audio codec to use with the transcoder
40 |
41 | Video codec
42 | Video codec to use with the transcoder
43 |
44 | Subtitle codec
45 | Subtitle codec to use with the transcoder
46 |
47 | Media container
48 | Prefered media container
49 |
50 | Playback
51 |
52 | HTTP port
53 | Enter Server HTTP Port
54 |
55 | Transcode stream
56 | Request a transcoded stream during live playback
57 |
58 | Prefer external player
59 | Use an external player for live streams (BSPlayer seems OK)
60 |
61 | Access denied
62 | Invalid response from server
63 | Can\'t connect to server
64 | Lost connection to server
65 |
66 | Loading…
67 | Please wait a few seconds…
68 |
69 | All channels
70 | Get more
71 | Season
72 | Episode
73 | Part
74 | Airing
75 | Rating
76 | Type
77 | Part
78 |
79 |
80 | - Movie/Drama
81 | - News/Current affairs
82 | - Show/Game show
83 | - Sports
84 | - Children\'s / Youth
85 | - Music/Ballet/Dance
86 | - Arts/Culture
87 | - Social/Political issues/Economics
88 | - Education/Science/Factual
89 | - Leisure hobbies
90 | - Misc
91 |
92 |
93 |
94 | - Movie/Drama
95 | - Detective/Thriller
96 | - Adventure/Western/War
97 | - Science Fiction/Fantasy/Horror
98 | - Comedy
99 | - Soap/Melodrama/Folkloric
100 | - Romance
101 | - Serious/ClassicalReligion/Historical
102 | - Adult Movie/Drama
103 |
104 |
105 |
106 | - News/Current affairs
107 | - News/Weather Report
108 | - Magazine
109 | - Documentary
110 | - Discussion/Interview/Debate
111 |
112 |
113 |
114 | - Show/Game show
115 | - Game show/Quiz/Contest
116 | - Variety
117 | - Talk
118 |
119 |
120 |
121 | - Sports
122 | - Special Event
123 | - Magazine
124 | - Football/Soccer
125 | - Tennis/Squash
126 | - Team sports
127 | - Athletics
128 | - Motor Sport
129 | - Water Sport
130 | - Winter Sports
131 | - Equestrian
132 | - Martial sports
133 |
134 |
135 |
136 | - Children\'s / Youth
137 | - Pre-school
138 | - Entertainment (6 to 14 year-olds)
139 | - Entertainment (10 to 16 year-olds)
140 | - Informational/Educational/Schools
141 | - Cartoons/Puppets
142 |
143 |
144 |
145 | - Music/Ballet/Dance
146 | - Rock/Pop
147 | - Serious music/Classical Music
148 | - Folk/Traditional music
149 | - Jazz
150 | - Musical/Opera
151 | - Ballet
152 |
153 |
154 |
155 | - Arts/Culture
156 | - Performing Arts
157 | - Fine Arts
158 | - Religion
159 | - Popular Culture/Tradital Arts
160 | - Literature
161 | - Film/Cinema
162 | - Experimental Film/Video
163 | - Broadcasting/Press
164 | - New Media
165 | - Magazine
166 | - Fashion
167 |
168 |
169 |
170 | - Social/Political issues/Economics
171 | - Magazine/Report/Domentary
172 | - Economics/Social Advisory
173 | - Remarkable People
174 |
175 |
176 |
177 |
178 | - Education/Science/Factual
179 | - Nature/Animals/Environment
180 | - Technology/Natural sciences
181 | - Medicine/Physiology/Psychology
182 | - Foreign Countries/Expeditions
183 | - Social/Spiritual Sciences
184 | - Further Education
185 | - Languages
186 |
187 |
188 |
189 | - Leisure hobbies
190 | - Tourism/Travel
191 | - Handicraft
192 | - Motoring
193 | - Fitness and Health
194 | - Cooking
195 | - Advertisement/Shopping
196 | - Gardening
197 |
198 |
199 |
200 | - Misc
201 | - Black and White
202 | - Unpublished
203 | - Live Broadcast
204 |
205 |
206 | Scheduled
207 | Recording
208 | Completed
209 | Missed
210 | Invalid
211 |
212 | Play
213 | No transmission
214 |
215 | Search the EPG
216 | today
217 |
--------------------------------------------------------------------------------
/res/layout/programme_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
18 |
19 |
24 |
25 |
31 |
32 |
39 |
40 |
44 |
45 |
46 |
53 |
54 |
55 |
60 |
61 |
67 |
68 |
74 |
75 |
79 |
80 |
84 |
85 |
89 |
90 |
91 |
92 |
96 |
97 |
104 |
105 |
106 |
112 |
113 |
119 |
123 |
124 |
125 |
129 |
130 |
137 |
138 |
139 |
145 |
146 |
152 |
153 |
157 |
158 |
159 |
160 |
164 |
165 |
172 |
173 |
174 |
180 |
181 |
187 |
188 |
192 |
193 |
203 |
204 |
210 |
211 |
212 |
213 |
214 |
215 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/htsp/SelectionThread.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.htsp;
20 |
21 | import android.util.Log;
22 | import java.io.IOException;
23 | import java.nio.channels.SelectionKey;
24 | import java.nio.channels.Selector;
25 | import java.nio.channels.ServerSocketChannel;
26 | import java.nio.channels.SocketChannel;
27 | import java.nio.channels.spi.AbstractSelectableChannel;
28 | import java.util.ArrayList;
29 | import java.util.HashMap;
30 | import java.util.Iterator;
31 | import java.util.concurrent.locks.Lock;
32 | import java.util.concurrent.locks.ReentrantLock;
33 |
34 | /**
35 | *
36 | * @author john-tornblom
37 | */
38 | public abstract class SelectionThread extends Thread {
39 |
40 | private static final String TAG = "SelectionThread";
41 | private Selector selector;
42 | private volatile boolean running;
43 | private HashMap regBuf;
44 | private Lock lock;
45 |
46 | public SelectionThread() {
47 | running = false;
48 | lock = new ReentrantLock();
49 | regBuf = new HashMap();
50 | }
51 |
52 | public void setRunning(boolean b) {
53 | try {
54 | lock.lock();
55 | running = false;
56 | } finally {
57 | lock.unlock();
58 | }
59 | }
60 |
61 | void close(AbstractSelectableChannel channel) throws IOException {
62 | lock.lock();
63 | try {
64 | regBuf.remove(channel);
65 | channel.close();
66 | } finally {
67 | lock.unlock();
68 | }
69 | }
70 |
71 | public void register(AbstractSelectableChannel channel, int ops, boolean b) {
72 | lock.lock();
73 | try {
74 | int oldOps = 0;
75 | if (regBuf.containsKey(channel)) {
76 | oldOps = regBuf.get(channel);
77 | }
78 | if (b) {
79 | ops |= oldOps;
80 | } else {
81 | ops = oldOps & ~ops;
82 | }
83 | regBuf.put(channel, ops);
84 | if (selector != null) {
85 | selector.wakeup();
86 | }
87 | } finally {
88 | lock.unlock();
89 | }
90 | }
91 |
92 | @Override
93 | public void run() {
94 | try {
95 | lock.lock();
96 | selector = Selector.open();
97 | running = true;
98 | } catch (IOException ex) {
99 | running = false;
100 | Log.e(TAG, "Can't open a selector", ex);
101 | } finally {
102 | lock.unlock();
103 | }
104 |
105 | while (running) {
106 | select(5000);
107 | }
108 |
109 | try {
110 | lock.lock();
111 | //Clean up
112 | for (SelectionKey key : selector.keys()) {
113 | try {
114 | key.channel().close();
115 | } catch (IOException ex) {
116 | Log.e(TAG, "Can't close channel", ex);
117 | key.cancel();
118 | }
119 |
120 | }
121 | try {
122 | selector.close();
123 | } catch (IOException ex) {
124 | Log.e(TAG, "Can't close selector", ex);
125 | }
126 | } finally {
127 | lock.unlock();
128 | }
129 | }
130 |
131 | private void select(int timeout) {
132 | try {
133 | selector.select(timeout);
134 | } catch (IOException ex) {
135 | Log.e(TAG, "Can't select socket", ex);
136 | return;
137 | }
138 |
139 | Iterator it = selector.selectedKeys().iterator();
140 |
141 | //Process the selected keys
142 | while (it.hasNext()) {
143 | SelectionKey selKey = (SelectionKey) it.next();
144 | it.remove();
145 | processTcpSelectionKey(selKey);
146 | }
147 |
148 | try {
149 | lock.lock();
150 | ArrayList tmp = new ArrayList();
151 | for (AbstractSelectableChannel ch : regBuf.keySet()) {
152 | try {
153 | int ops = regBuf.get(ch);
154 | ch.register(selector, ops);
155 | } catch (Throwable t) {
156 | tmp.add(ch);
157 | Log.e(TAG, "Can't register channel", t);
158 | if (ch instanceof SocketChannel) {
159 | onError((SocketChannel) ch);
160 | }
161 | }
162 | }
163 | for (AbstractSelectableChannel ch : tmp) {
164 | regBuf.remove(ch);
165 | }
166 | } finally {
167 | lock.unlock();
168 | }
169 | }
170 |
171 | private void processTcpSelectionKey(SelectionKey selKey) {
172 | //Incomming connection established
173 | if (selKey.isValid() && selKey.isAcceptable()) {
174 | try {
175 | ServerSocketChannel ssChannel = (ServerSocketChannel) selKey.channel();
176 | SocketChannel sChannel = ssChannel.accept();
177 | if (sChannel != null) {
178 | sChannel.configureBlocking(false);
179 | try {
180 | onAccept(sChannel);
181 | } catch (Throwable t) {
182 | Log.e(TAG, "Can't establish connection", t);
183 | onError(sChannel);
184 | return;
185 | }
186 | }
187 | } catch (Throwable t) {
188 | Log.e(TAG, "Can't establish connection", t);
189 | return;
190 | }
191 | }
192 |
193 | //Outgoing connection established
194 | if (selKey.isValid() && selKey.isConnectable()) {
195 | try {
196 | SocketChannel sChannel = (SocketChannel) selKey.channel();
197 | if (!sChannel.finishConnect()) {
198 | onError(sChannel);
199 | selKey.cancel();
200 | return;
201 | }
202 | try {
203 | onConnect(sChannel);
204 | } catch (Throwable t) {
205 | Log.e(TAG, "Can't establish connection", t);
206 | onError(sChannel);
207 | selKey.cancel();
208 | return;
209 | }
210 | } catch (Throwable t) {
211 | Log.e(TAG, "Can't establish connection", t);
212 | selKey.cancel();
213 | return;
214 | }
215 | }
216 |
217 | //Incomming data
218 | if (selKey.isValid() && selKey.isReadable()) {
219 | SocketChannel sChannel = (SocketChannel) selKey.channel();
220 | try {
221 | onReadable(sChannel);
222 | } catch (Throwable t) {
223 | Log.e(TAG, "Can't read message", t);
224 | onError(sChannel);
225 | selKey.cancel();
226 | return;
227 | }
228 | }
229 |
230 | //Clear to send
231 | if (selKey.isValid() && selKey.isWritable()) {
232 | SocketChannel sChannel = (SocketChannel) selKey.channel();
233 | try {
234 | onWrtiable(sChannel);
235 | } catch (Throwable t) {
236 | Log.e(TAG, "Can't send message", t);
237 | onError(sChannel);
238 | selKey.cancel();
239 | return;
240 | }
241 | }
242 | }
243 |
244 | private void onAccept(SocketChannel ch) throws Exception {
245 | onEvent(SelectionKey.OP_ACCEPT, ch);
246 | }
247 |
248 | private void onConnect(SocketChannel ch) throws Exception {
249 | onEvent(SelectionKey.OP_CONNECT, ch);
250 | }
251 |
252 | private void onReadable(SocketChannel ch) throws Exception {
253 | onEvent(SelectionKey.OP_READ, ch);
254 | }
255 |
256 | private void onError(SocketChannel ch) {
257 | try {
258 | lock.lock();
259 | ch.close();
260 | regBuf.remove(ch);
261 | } catch (Exception ex) {
262 | Log.e(TAG, null, ex);
263 | } finally {
264 | lock.unlock();
265 | }
266 | try {
267 | onEvent(-1, ch);
268 | } catch (Exception ex) {
269 | }
270 | }
271 |
272 | private void onWrtiable(SocketChannel ch) throws Exception {
273 | onEvent(SelectionKey.OP_WRITE, ch);
274 | }
275 |
276 | public abstract void onEvent(int selectionKey, SocketChannel ch) throws Exception;
277 | }
278 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/ProgrammeActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide;
20 |
21 | import android.app.Activity;
22 | import android.content.Intent;
23 | import android.content.SharedPreferences;
24 | import android.os.Bundle;
25 | import android.preference.PreferenceManager;
26 | import android.text.format.DateFormat;
27 | import android.util.SparseArray;
28 | import android.view.Menu;
29 | import android.view.MenuItem;
30 | import android.view.View;
31 | import android.view.Window;
32 | import android.widget.ImageView;
33 | import android.widget.RatingBar;
34 | import android.widget.TextView;
35 |
36 | import org.tvheadend.tvhguide.R;
37 | import org.tvheadend.tvhguide.R.string;
38 | import org.tvheadend.tvhguide.htsp.HTSService;
39 | import org.tvheadend.tvhguide.intent.SearchEPGIntent;
40 | import org.tvheadend.tvhguide.intent.SearchIMDbIntent;
41 | import org.tvheadend.tvhguide.model.Channel;
42 | import org.tvheadend.tvhguide.model.Programme;
43 | import org.tvheadend.tvhguide.model.SeriesInfo;
44 |
45 | /**
46 | *
47 | * @author john-tornblom
48 | */
49 | public class ProgrammeActivity extends Activity {
50 |
51 | private Programme programme;
52 |
53 | @Override
54 | public void onCreate(Bundle savedInstanceState) {
55 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
56 | Boolean theme = prefs.getBoolean("lightThemePref", false);
57 | setTheme(theme ? R.style.CustomTheme_Light : R.style.CustomTheme);
58 |
59 | super.onCreate(savedInstanceState);
60 |
61 | TVHGuideApplication app = (TVHGuideApplication) getApplication();
62 | Channel channel = app.getChannel(getIntent().getLongExtra("channelId", 0));
63 | if (channel == null) {
64 | finish();
65 | return;
66 | }
67 |
68 | long eventId = getIntent().getLongExtra("eventId", 0);
69 | for (Programme p : channel.epg) {
70 | if (p.id == eventId) {
71 | programme = p;
72 | break;
73 | }
74 | }
75 |
76 | if (programme == null) {
77 | finish();
78 | return;
79 | }
80 |
81 | requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
82 |
83 | setContentView(R.layout.programme_layout);
84 |
85 | getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.programme_title);
86 | TextView t = (TextView) findViewById(R.id.ct_title);
87 | t.setText(channel.name);
88 |
89 | if (channel.iconBitmap != null) {
90 | ImageView iv = (ImageView) findViewById(R.id.ct_logo);
91 | iv.setImageBitmap(channel.iconBitmap);
92 | }
93 |
94 |
95 | TextView text = (TextView) findViewById(R.id.pr_title);
96 | text.setText(programme.title);
97 |
98 | text = (TextView) findViewById(R.id.pr_channel);
99 | text.setText(channel.name);
100 |
101 | text = (TextView) findViewById(R.id.pr_airing);
102 | text.setText(
103 | DateFormat.getLongDateFormat(text.getContext()).format(programme.start)
104 | + " "
105 | + DateFormat.getTimeFormat(text.getContext()).format(programme.start)
106 | + " - "
107 | + DateFormat.getTimeFormat(text.getContext()).format(programme.stop));
108 |
109 |
110 | if(programme.summary.length() == 0 && programme.description.length() == 0) {
111 | View v = findViewById(R.id.pr_summay_and_desc_layout);
112 | v.setVisibility(View.GONE);
113 | } else {
114 | text = (TextView) findViewById(R.id.pr_summary);
115 | text.setText(programme.summary);
116 | if(programme.summary.length() == 0)
117 | text.setVisibility(View.GONE);
118 |
119 | text = (TextView) findViewById(R.id.pr_desc);
120 | text.setText(programme.description);
121 | if(programme.description.length() == 0)
122 | text.setVisibility(View.GONE);
123 | }
124 |
125 | String s = buildSeriesInfoString(programme.seriesInfo);
126 | if(s.length() > 0) {
127 | text = (TextView) findViewById(R.id.pr_series_info);
128 | text.setText(s);
129 | } else {
130 | View v = findViewById(R.id.pr_series_info_row);
131 | v.setVisibility(View.GONE);
132 | v = findViewById(R.id.pr_series_info_sep);
133 | v.setVisibility(View.GONE);
134 | }
135 |
136 | SparseArray contentTypes = TVHGuideApplication.getContentTypes(this);
137 | s = contentTypes.get(programme.contentType, "");
138 | if(s.length() > 0) {
139 | text = (TextView) findViewById(R.id.pr_content_type);
140 | text.setText(s);
141 | } else {
142 | View v = findViewById(R.id.pr_content_type_row);
143 | v.setVisibility(View.GONE);
144 | v = findViewById(R.id.pr_content_type_sep);
145 | v.setVisibility(View.GONE);
146 | }
147 |
148 | if(programme.starRating > 0) {
149 | RatingBar starRating = (RatingBar)findViewById(R.id.pr_star_rating);
150 | starRating.setRating((float)programme.starRating / 10.0f);
151 |
152 | text = (TextView) findViewById(R.id.pr_star_rating_txt);
153 | text.setText("("
154 | + programme.starRating
155 | + "/"
156 | + 100
157 | + ")");
158 | } else {
159 | View v = findViewById(R.id.pr_star_rating_row);
160 | v.setVisibility(View.GONE);
161 | }
162 | }
163 |
164 |
165 | public String buildSeriesInfoString(SeriesInfo info) {
166 | if (info.onScreen != null && info.onScreen.length() > 0)
167 | return info.onScreen;
168 |
169 | String s = "";
170 | String season = this.getResources().getString(string.pr_season);
171 | String episode = this.getResources().getString(string.pr_episode);
172 | String part = this.getResources().getString(string.pr_part);
173 |
174 | if(info.onScreen.length() > 0) {
175 | return info.onScreen;
176 | }
177 |
178 | if (info.seasonNumber > 0) {
179 | if (s.length() > 0)
180 | s += ", ";
181 | s += String.format("%s %02d", season.toLowerCase(), info.seasonNumber);
182 | if(info.seasonCount > 0)
183 | s += String.format("/%02d", info.seasonCount);
184 | }
185 | if (info.episodeNumber > 0) {
186 | if (s.length() > 0)
187 | s += ", ";
188 | s += String.format("%s %02d", episode.toLowerCase(), info.episodeNumber);
189 | if(info.episodeCount > 0)
190 | s += String.format("/%02d", info.episodeCount);
191 | }
192 | if (info.partNumber > 0) {
193 | if (s.length() > 0)
194 | s += ", ";
195 | s += String.format("%s %d", part.toLowerCase(), info.partNumber);
196 | if(info.partCount > 0)
197 | s += String.format("/%02d", info.partCount);
198 | }
199 |
200 | if(s.length() > 0) {
201 | s = s.substring(0,1).toUpperCase() + s.substring(1);
202 | }
203 |
204 | return s;
205 | }
206 |
207 | @Override
208 | public boolean onCreateOptionsMenu(Menu menu) {
209 | MenuItem item = null;
210 |
211 | if (programme.title != null) {
212 | item = menu.add(Menu.NONE, android.R.string.search_go, Menu.NONE, android.R.string.search_go);
213 | item.setIntent(new SearchEPGIntent(this, programme.title));
214 | item.setIcon(android.R.drawable.ic_menu_search);
215 |
216 | item = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, "IMDb");
217 | item.setIntent(new SearchIMDbIntent(this, programme.title));
218 | item.setIcon(android.R.drawable.ic_menu_info_details);
219 | }
220 |
221 | Intent intent = new Intent(this, HTSService.class);
222 |
223 | if (programme.recording == null) {
224 | intent.setAction(HTSService.ACTION_DVR_ADD);
225 | intent.putExtra("eventId", programme.id);
226 | intent.putExtra("channelId", programme.channel.id);
227 | item = menu.add(Menu.NONE, R.string.menu_record, Menu.NONE, R.string.menu_record);
228 | item.setIcon(android.R.drawable.ic_menu_save);
229 | } else if (programme.isRecording() || programme.isScheduled()) {
230 | intent.setAction(HTSService.ACTION_DVR_CANCEL);
231 | intent.putExtra("id", programme.recording.id);
232 | item = menu.add(Menu.NONE, R.string.menu_record_cancel, Menu.NONE, R.string.menu_record_cancel);
233 | item.setIcon(android.R.drawable.ic_menu_close_clear_cancel);
234 | } else {
235 | intent.setAction(HTSService.ACTION_DVR_DELETE);
236 | intent.putExtra("id", programme.recording.id);
237 | item = menu.add(Menu.NONE, R.string.menu_record_remove, Menu.NONE, R.string.menu_record_remove);
238 | item.setIcon(android.R.drawable.ic_menu_delete);
239 | }
240 |
241 | item.setIntent(intent);
242 |
243 | return true;
244 | }
245 |
246 | @Override
247 | public boolean onPrepareOptionsMenu(Menu menu) {
248 | boolean rebuild = false;
249 | if (programme.recording == null) {
250 | rebuild = menu.findItem(R.string.menu_record) == null;
251 | } else if (programme.isRecording() || programme.isScheduled()) {
252 | rebuild = menu.findItem(R.string.menu_record_cancel) == null;
253 | } else {
254 | rebuild = menu.findItem(R.string.menu_record_remove) == null;
255 | }
256 |
257 | if (rebuild) {
258 | menu.clear();
259 | return onCreateOptionsMenu(menu);
260 | }
261 |
262 | return true;
263 | }
264 |
265 | @Override
266 | public boolean onOptionsItemSelected(MenuItem item) {
267 | switch (item.getItemId()) {
268 | case R.string.menu_record_remove:
269 | case R.string.menu_record_cancel:
270 | case R.string.menu_record:
271 | startService(item.getIntent());
272 | return true;
273 | default:
274 | return super.onOptionsItemSelected(item);
275 | }
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/htsp/HTSConnection.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.htsp;
20 |
21 | import android.util.Log;
22 | import java.io.IOException;
23 | import java.net.InetSocketAddress;
24 | import java.nio.ByteBuffer;
25 | import java.nio.channels.SelectionKey;
26 | import java.nio.channels.Selector;
27 | import java.nio.channels.SocketChannel;
28 | import java.security.MessageDigest;
29 | import java.security.NoSuchAlgorithmException;
30 | import java.util.HashMap;
31 | import java.util.Iterator;
32 | import java.util.LinkedList;
33 | import java.util.Map;
34 | import java.util.concurrent.locks.Lock;
35 | import java.util.concurrent.locks.ReentrantLock;
36 |
37 | /**
38 | *
39 | * @author john-tornblom
40 | */
41 | public class HTSConnection extends Thread {
42 |
43 | public static final int TIMEOUT_ERROR = 1;
44 | public static final int CONNECTION_REFUSED_ERROR = 2;
45 | public static final int CONNECTION_LOST_ERROR = 3;
46 | public static final int HTS_AUTH_ERROR = 4;
47 | public static final int HTS_MESSAGE_ERROR = 5;
48 | private static final String TAG = "HTSPConnection";
49 | private volatile boolean running;
50 | private Lock lock;
51 | private SocketChannel socketChannel;
52 | private ByteBuffer inBuf;
53 | private int seq;
54 | private String clientName;
55 | private String clientVersion;
56 | private int protocolVersion;
57 | private String webRoot;
58 |
59 | private HTSConnectionListener listener;
60 | private Map responseHandelers;
61 | private LinkedList messageQueue;
62 | private boolean auth;
63 | private Selector selector;
64 |
65 | public HTSConnection(HTSConnectionListener listener, String clientName, String clientVersion) {
66 | running = false;
67 | lock = new ReentrantLock();
68 | inBuf = ByteBuffer.allocateDirect(1024 * 1024);
69 | inBuf.limit(4);
70 | responseHandelers = new HashMap();
71 | messageQueue = new LinkedList();
72 |
73 | this.listener = listener;
74 | this.clientName = clientName;
75 | this.clientVersion = clientVersion;
76 | }
77 |
78 | public void setRunning(boolean b) {
79 | try {
80 | lock.lock();
81 | running = false;
82 | } finally {
83 | lock.unlock();
84 | }
85 | }
86 |
87 | //sychronized, blocking connect
88 | public void open(String hostname, int port) {
89 | if (running) {
90 | return;
91 | }
92 |
93 | final Object signal = new Object();
94 |
95 | lock.lock();
96 | try {
97 | selector = Selector.open();
98 | socketChannel = SocketChannel.open();
99 | socketChannel.configureBlocking(false);
100 | socketChannel.socket().setKeepAlive(true);
101 | socketChannel.socket().setSoTimeout(5000);
102 | socketChannel.register(selector, SelectionKey.OP_CONNECT, signal);
103 | socketChannel.connect(new InetSocketAddress(hostname, port));
104 |
105 | running = true;
106 | start();
107 | } catch (Exception ex) {
108 | Log.e(TAG, "Can't open connection", ex);
109 | listener.onError(CONNECTION_REFUSED_ERROR);
110 | return;
111 | } finally {
112 | lock.unlock();
113 | }
114 |
115 | synchronized (signal) {
116 | try {
117 | signal.wait(5000);
118 | if (socketChannel.isConnectionPending()) {
119 | listener.onError(TIMEOUT_ERROR);
120 | close();
121 | }
122 | } catch (InterruptedException ex) {
123 | }
124 | }
125 | }
126 |
127 | public boolean isConnected() {
128 | return socketChannel != null
129 | && socketChannel.isOpen()
130 | && socketChannel.isConnected()
131 | && running;
132 | }
133 |
134 | //sycnronized, blocking auth
135 | public void authenticate(String username, final String password) {
136 | if (auth || !running) {
137 | return;
138 | }
139 |
140 | auth = false;
141 | final HTSMessage authMessage = new HTSMessage();
142 | authMessage.setMethod("enableAsyncMetadata");
143 | authMessage.putField("username", username);
144 | final HTSResponseHandler authHandler = new HTSResponseHandler() {
145 |
146 | public void handleResponse(HTSMessage response) {
147 | auth = response.getInt("noaccess", 0) != 1;
148 | if (!auth) {
149 | listener.onError(HTS_AUTH_ERROR);
150 | }
151 | synchronized (authMessage) {
152 | authMessage.notify();
153 | }
154 | }
155 | };
156 |
157 | HTSMessage helloMessage = new HTSMessage();
158 | helloMessage.setMethod("hello");
159 | helloMessage.putField("clientname", this.clientName);
160 | helloMessage.putField("clientversion", this.clientVersion);
161 | helloMessage.putField("htspversion", HTSMessage.HTSP_VERSION);
162 | helloMessage.putField("username", username);
163 | sendMessage(helloMessage, new HTSResponseHandler() {
164 |
165 | public void handleResponse(HTSMessage response) {
166 |
167 | protocolVersion = response.getInt("htspversion");
168 | webRoot = response.getString("webroot", "");
169 |
170 | MessageDigest md;
171 | try {
172 | md = MessageDigest.getInstance("SHA1");
173 | md.update(password.getBytes());
174 | md.update(response.getByteArray("challenge"));
175 | authMessage.putField("digest", md.digest());
176 | sendMessage(authMessage, authHandler);
177 | } catch (NoSuchAlgorithmException ex) {
178 | return;
179 | }
180 | }
181 | });
182 |
183 | synchronized (authMessage) {
184 | try {
185 | authMessage.wait(5000);
186 | if (!auth) {
187 | listener.onError(TIMEOUT_ERROR);
188 | }
189 | return;
190 | } catch (InterruptedException ex) {
191 | return;
192 | }
193 | }
194 | }
195 |
196 | public boolean isAuthenticated() {
197 | return auth;
198 | }
199 |
200 | public void sendMessage(HTSMessage message, HTSResponseHandler listener) {
201 | if (!isConnected()) {
202 | return;
203 | }
204 |
205 | lock.lock();
206 | try {
207 | seq++;
208 | message.putField("seq", seq);
209 | responseHandelers.put(seq, listener);
210 | socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ | SelectionKey.OP_CONNECT);
211 | messageQueue.add(message);
212 | selector.wakeup();
213 | } catch (Exception ex) {
214 | Log.e(TAG, "Can't transmit message", ex);
215 | this.listener.onError(ex);
216 | } finally {
217 | lock.unlock();
218 | }
219 | }
220 |
221 | public void close() {
222 | lock.lock();
223 | try {
224 | responseHandelers.clear();
225 | messageQueue.clear();
226 | auth = false;
227 | running = false;
228 | socketChannel.register(selector, 0);
229 | socketChannel.close();
230 | } catch (Exception ex) {
231 | Log.e(TAG, "Can't close connection", ex);
232 | } finally {
233 | lock.unlock();
234 | }
235 | }
236 |
237 | @Override
238 | public void run() {
239 | while (running) {
240 | try {
241 | selector.select(5000);
242 | } catch (IOException ex) {
243 | listener.onError(ex);
244 | running = false;
245 | continue;
246 | }
247 |
248 | lock.lock();
249 |
250 | try {
251 | Iterator it = selector.selectedKeys().iterator();
252 | while (it.hasNext()) {
253 | SelectionKey selKey = (SelectionKey) it.next();
254 | it.remove();
255 | processTcpSelectionKey(selKey);
256 | }
257 |
258 | int ops = SelectionKey.OP_READ;
259 | if (!messageQueue.isEmpty()) {
260 | ops |= SelectionKey.OP_WRITE;
261 | }
262 | socketChannel.register(selector, ops);
263 | } catch (Exception ex) {
264 | Log.e(TAG, "Can't read message", ex);
265 | listener.onError(ex);
266 | running = false;
267 | } finally {
268 | lock.unlock();
269 | }
270 | }
271 |
272 | close();
273 | }
274 |
275 | private void processTcpSelectionKey(SelectionKey selKey) throws IOException {
276 | if (selKey.isConnectable() && selKey.isValid()) {
277 | SocketChannel sChannel = (SocketChannel) selKey.channel();
278 | sChannel.finishConnect();
279 | final Object signal = selKey.attachment();
280 | synchronized (signal) {
281 | signal.notify();
282 | }
283 | sChannel.register(selector, SelectionKey.OP_READ);
284 | }
285 |
286 | if (selKey.isReadable() && selKey.isValid()) {
287 | SocketChannel sChannel = (SocketChannel) selKey.channel();
288 | int len = sChannel.read(inBuf);
289 | if (len < 0) {
290 | throw new IOException("Server went down");
291 | }
292 |
293 | HTSMessage msg = HTSMessage.parse(inBuf);
294 | if (msg != null) {
295 | handleMessage(msg);
296 | }
297 | }
298 | if (selKey.isWritable() && selKey.isValid()) {
299 | SocketChannel sChannel = (SocketChannel) selKey.channel();
300 | HTSMessage msg = messageQueue.poll();
301 | if (msg != null) {
302 | msg.transmit(sChannel);
303 | }
304 | }
305 | }
306 |
307 | private void handleMessage(HTSMessage msg) {
308 | if (msg.containsField("seq")) {
309 | int respSeq = msg.getInt("seq");
310 | HTSResponseHandler handler = responseHandelers.get(respSeq);
311 | responseHandelers.remove(respSeq);
312 |
313 | if (handler != null) {
314 | synchronized (handler) {
315 | handler.handleResponse(msg);
316 | }
317 | return;
318 | }
319 | }
320 |
321 | listener.onMessage(msg);
322 | }
323 |
324 | public int getProtocolVersion() {
325 | return this.protocolVersion;
326 | }
327 |
328 | public String getWebRoot() {
329 | return this.webRoot;
330 | }
331 | }
332 |
--------------------------------------------------------------------------------
/src/org/tvheadend/tvhguide/htsp/HTSMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 John Törnblom
3 | *
4 | * This file is part of TVHGuide.
5 | *
6 | * TVHGuide is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * TVHGuide is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with TVHGuide. If not, see .
18 | */
19 | package org.tvheadend.tvhguide.htsp;
20 |
21 | import java.io.IOException;
22 | import java.math.BigInteger;
23 | import java.nio.ByteBuffer;
24 | import java.nio.channels.SocketChannel;
25 | import java.util.ArrayList;
26 | import java.util.Collection;
27 | import java.util.Date;
28 | import java.util.HashMap;
29 | import java.util.List;
30 | import java.util.Map;
31 |
32 | /**
33 | *
34 | * @author john-tornblom
35 | */
36 | public class HTSMessage extends HashMap {
37 |
38 | public static final long HTSP_VERSION = 8;
39 | private static final byte HMF_MAP = 1;
40 | private static final byte HMF_S64 = 2;
41 | private static final byte HMF_STR = 3;
42 | private static final byte HMF_BIN = 4;
43 | private static final byte HMF_LIST = 5;
44 | private ByteBuffer buf;
45 |
46 | public void putField(String name, Object value) {
47 | if (value != null) {
48 | put(name, value);
49 | }
50 | }
51 |
52 | public void setMethod(String name) {
53 | put("method", name);
54 | }
55 |
56 | public String getMethod() {
57 | return getString("method", "");
58 | }
59 |
60 | public boolean containsField(String name) {
61 | return containsKey(name);
62 | }
63 |
64 | public BigInteger getBigInteger(String name) {
65 | return (BigInteger) get(name);
66 | }
67 |
68 | public long getLong(String name) {
69 | return getBigInteger(name).longValue();
70 | }
71 |
72 | public long getLong(String name, long std) {
73 | if (!containsField(name)) {
74 | return std;
75 | }
76 | return getLong(name);
77 | }
78 |
79 | public int getInt(String name) {
80 | return getBigInteger(name).intValue();
81 | }
82 |
83 | public int getInt(String name, int std) {
84 | if (!containsField(name)) {
85 | return std;
86 | }
87 | return getInt(name);
88 | }
89 |
90 | public String getString(String name, String std) {
91 | if (!containsField(name)) {
92 | return std;
93 | }
94 | return getString(name);
95 | }
96 |
97 | public String getString(String name) {
98 | Object obj = get(name);
99 | if (obj == null) {
100 | return null;
101 | }
102 | return obj.toString();
103 | }
104 |
105 | public List getLongList(String name) {
106 | ArrayList list = new ArrayList();
107 |
108 | if (!containsField(name)) {
109 | return list;
110 | }
111 |
112 | for (Object obj : (List) get(name)) {
113 | if (obj instanceof BigInteger) {
114 | list.add(((BigInteger) obj).longValue());
115 | }
116 | }
117 |
118 | return list;
119 | }
120 |
121 | List getLongList(String name, List std) {
122 | if (!containsField(name)) {
123 | return std;
124 | }
125 |
126 | return getLongList(name);
127 | }
128 |
129 | public List getIntList(String name) {
130 | ArrayList list = new ArrayList();
131 |
132 | if (!containsField(name)) {
133 | return list;
134 | }
135 |
136 | for (Object obj : (List) get(name)) {
137 | if (obj instanceof BigInteger) {
138 | list.add(((BigInteger) obj).intValue());
139 | }
140 | }
141 |
142 | return list;
143 | }
144 |
145 | List getIntList(String name, List std) {
146 | if (!containsField(name)) {
147 | return std;
148 | }
149 |
150 | return getIntList(name);
151 | }
152 |
153 | public List getList(String name) {
154 | return (List) get(name);
155 | }
156 |
157 | public byte[] getByteArray(String name) {
158 | return (byte[]) get(name);
159 | }
160 |
161 | public Date getDate(String name) {
162 | return new Date(getLong(name) * 1000);
163 | }
164 |
165 | public boolean transmit(SocketChannel ch) throws IOException {
166 | if (buf == null) {
167 | byte[] data = serializeBinary(this);
168 | int len = data.length;
169 | buf = ByteBuffer.allocateDirect(len + 4);
170 |
171 | buf.put((byte) ((len >> 24) & 0xFF));
172 | buf.put((byte) ((len >> 16) & 0xFF));
173 | buf.put((byte) ((len >> 8) & 0xFF));
174 | buf.put((byte) ((len) & 0xFF));
175 | buf.put(data);
176 | buf.flip();
177 | }
178 |
179 | if (ch.write(buf) < 0) {
180 | throw new IOException("Server went down");
181 | }
182 |
183 | if (buf.hasRemaining()) {
184 | return false;
185 | } else {
186 | buf.flip();
187 | return true;
188 | }
189 | }
190 |
191 | public static String getHexString(byte[] b) throws Exception {
192 | String result = "";
193 | for (int i = 0; i < b.length; i++) {
194 | result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1);
195 | }
196 | return result;
197 | }
198 |
199 | private static byte[] toByteArray(BigInteger big) {
200 | byte[] b = big.toByteArray();
201 | byte b1[] = new byte[b.length];
202 |
203 | for (int i = 0; i < b.length; i++) {
204 | b1[i] = b[b.length - 1 - i];
205 | }
206 |
207 | return b1;
208 | }
209 |
210 | private static BigInteger toBigInteger(byte b[]) {
211 | byte b1[] = new byte[b.length + 1];
212 |
213 | for (int i = 0; i < b.length; i++) {
214 | b1[i + 1] = b[b.length - 1 - i];
215 | }
216 |
217 | return new BigInteger(b1);
218 | }
219 |
220 | private static long uIntToLong(byte b1, byte b2, byte b3, byte b4) {
221 | long i = 0;
222 | i <<= 8;
223 | i ^= b1 & 0xFF;
224 | i <<= 8;
225 | i ^= b2 & 0xFF;
226 | i <<= 8;
227 | i ^= b3 & 0xFF;
228 | i <<= 8;
229 | i ^= b4 & 0xFF;
230 | return i;
231 | }
232 |
233 | public static HTSMessage parse(ByteBuffer buf) throws IOException {
234 | long len;
235 |
236 | if (buf.position() < 4) {
237 | return null;
238 | }
239 |
240 | len = uIntToLong(buf.get(0), buf.get(1), buf.get(2), buf.get(3));
241 |
242 | if (len + 4 > buf.capacity()) {
243 | buf.clear();
244 | throw new IOException("Mesage is to long");
245 | }
246 |
247 | if (buf.limit() == 4) {
248 | buf.limit((int) (4 + len));
249 | }
250 |
251 | //Message not yet fully read
252 | if (buf.position() < len + 4) {
253 | return null;
254 | }
255 |
256 | buf.flip();
257 | buf.getInt(); //drops 4 bytes
258 | HTSMessage msg = deserializeBinary(buf);
259 |
260 | buf.limit(4);
261 | buf.position(0);
262 | return msg;
263 | }
264 |
265 | private static byte[] serializeBinary(String name, Object value) throws IOException {
266 | byte[] bName = name.getBytes();
267 | byte[] bData = new byte[0];
268 | byte type;
269 |
270 | if (value instanceof String) {
271 | type = HTSMessage.HMF_STR;
272 | bData = ((String) value).getBytes();
273 | } else if (value instanceof BigInteger) {
274 | type = HTSMessage.HMF_S64;
275 | bData = toByteArray((BigInteger) value);
276 | } else if (value instanceof Integer) {
277 | type = HTSMessage.HMF_S64;
278 | bData = toByteArray(BigInteger.valueOf((Integer) value));
279 | } else if (value instanceof Long) {
280 | type = HTSMessage.HMF_S64;
281 | bData = toByteArray(BigInteger.valueOf((Long) value));
282 | } else if (value instanceof byte[]) {
283 | type = HTSMessage.HMF_BIN;
284 | bData = (byte[]) value;
285 | } else if (value instanceof Map) {
286 | type = HTSMessage.HMF_MAP;
287 | bData = serializeBinary((Map) value);
288 | } else if (value instanceof Collection) {
289 | type = HTSMessage.HMF_LIST;
290 | bData = serializeBinary((Collection) value);
291 | } else if (value == null) {
292 | throw new IOException("HTSP doesn't support null values");
293 | } else {
294 | throw new IOException("Unhandled class for " + name + ": " + value
295 | + " (" + value.getClass().getSimpleName() + ")");
296 | }
297 |
298 | byte[] buf = new byte[1 + 1 + 4 + bName.length + bData.length];
299 | buf[0] = type;
300 | buf[1] = (byte) (bName.length & 0xFF);
301 | buf[2] = (byte) ((bData.length >> 24) & 0xFF);
302 | buf[3] = (byte) ((bData.length >> 16) & 0xFF);
303 | buf[4] = (byte) ((bData.length >> 8) & 0xFF);
304 | buf[5] = (byte) ((bData.length) & 0xFF);
305 |
306 | System.arraycopy(bName, 0, buf, 6, bName.length);
307 | System.arraycopy(bData, 0, buf, 6 + bName.length, bData.length);
308 |
309 | return buf;
310 | }
311 |
312 | private static byte[] serializeBinary(Collection list) throws IOException {
313 | ByteBuffer buf = ByteBuffer.allocate(Short.MAX_VALUE);
314 |
315 | for (Object value : list) {
316 | byte[] sub = serializeBinary("", value);
317 | buf.put(sub);
318 | }
319 |
320 | byte[] bBuf = new byte[buf.position()];
321 | buf.flip();
322 | buf.get(bBuf);
323 |
324 | return bBuf;
325 | }
326 |
327 | private static byte[] serializeBinary(Map map) throws IOException {
328 | ByteBuffer buf = ByteBuffer.allocate(Short.MAX_VALUE);
329 |
330 | for (Object key : map.keySet()) {
331 | Object value = map.get(key);
332 | byte[] sub = serializeBinary(key.toString(), value);
333 | buf.put(sub);
334 | }
335 |
336 | byte[] bBuf = new byte[buf.position()];
337 | buf.flip();
338 | buf.get(bBuf);
339 |
340 | return bBuf;
341 | }
342 |
343 | private static HTSMessage deserializeBinary(ByteBuffer buf) throws IOException {
344 | byte type, namelen;
345 | long datalen;
346 |
347 | HTSMessage msg = new HTSMessage();
348 | int cnt = 0;
349 |
350 | while (buf.hasRemaining()) {
351 | type = buf.get();
352 | namelen = buf.get();
353 | datalen = uIntToLong(buf.get(), buf.get(), buf.get(), buf.get());
354 |
355 | if (datalen > Integer.MAX_VALUE) {
356 | throw new IOException("Would get precision losses ;(");
357 | }
358 | if (buf.limit() < namelen + datalen) {
359 | throw new IOException("Buffer limit exceeded");
360 | }
361 |
362 | //Get the key for the map (the name)
363 | String name = null;
364 | if (namelen == 0) {
365 | name = Integer.toString(cnt++);
366 | } else {
367 | byte[] bName = new byte[namelen];
368 | buf.get(bName);
369 | name = new String(bName);
370 | }
371 |
372 | //Get the actual content
373 | Object obj = null;
374 | byte[] bData = new byte[(int) datalen]; //Should be long?
375 | buf.get(bData);
376 |
377 | switch (type) {
378 | case HTSMessage.HMF_STR: {
379 | obj = new String(bData);
380 | break;
381 | }
382 | case HMF_BIN: {
383 | obj = bData;
384 | break;
385 | }
386 | case HMF_S64: {
387 | obj = toBigInteger(bData);
388 | break;
389 | }
390 | case HMF_MAP: {
391 | ByteBuffer sub = ByteBuffer.allocateDirect((int) datalen);
392 | sub.put(bData);
393 | sub.flip();
394 | obj = deserializeBinary(sub);
395 | break;
396 | }
397 | case HMF_LIST: {
398 | ByteBuffer sub = ByteBuffer.allocateDirect((int) datalen);
399 | sub.put(bData);
400 | sub.flip();
401 | obj = new ArrayList