copy leanback module
1
leanback/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/build
|
96
leanback/build.gradle
Normal file
|
@ -0,0 +1,96 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
defaultConfig {
|
||||
applicationId "de.nicidienase.chaosflix"
|
||||
minSdkVersion rootProject.ext.minSDK
|
||||
targetSdkVersion rootProject.ext.targetSDK
|
||||
// odd for touch, even for leanback
|
||||
versionCode 10
|
||||
versionName "0.2.2"
|
||||
multiDexEnabled true
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
useProguard false
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
useProguard false
|
||||
// shrinkResources true
|
||||
// minifyEnabled true
|
||||
useProguard false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),
|
||||
'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
exclude 'META-INF/ASL2.0'
|
||||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/license.txt'
|
||||
exclude 'META-INF/NOTICE'
|
||||
exclude 'META-INF/notice.txt'
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
defaultConfig {
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
buildToolsVersion '26.0.2'
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "de.nicidienase.chaosflix:common:1.0.1-SNAPSHOT"
|
||||
compile fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "com.android.support:recyclerview-v7:${rootProject.ext.supportLibraryVersion}"
|
||||
implementation "com.android.support:cardview-v7:${rootProject.ext.supportLibraryVersion}"
|
||||
implementation "com.android.support:leanback-v17:${rootProject.ext.supportLibraryVersion}"
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibraryVersion}"
|
||||
implementation "com.android.support:design:${rootProject.ext.supportLibraryVersion}"
|
||||
implementation 'com.android.support:multidex:1.0.2'
|
||||
|
||||
implementation 'com.google.android.exoplayer:exoplayer:r2.5.2'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.1.3'
|
||||
|
||||
implementation 'com.github.satyan:sugar:1.5'
|
||||
implementation 'com.github.bumptech.glide:glide:3.8.0'
|
||||
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
|
||||
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
|
||||
|
||||
implementation 'joda-time:joda-time:2.9.9'
|
||||
implementation 'org.joda:joda-convert:1.8'
|
||||
|
||||
implementation 'com.jakewharton:butterknife:8.5.1'
|
||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
|
||||
debugImplementation 'com.facebook.stetho:stetho:1.4.2'
|
||||
debugImplementation 'com.facebook.stetho:stetho-okhttp:1.4.2'
|
||||
debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.4.2'
|
||||
androidTestImplementation('com.android.support.test:rules:0.5') {
|
||||
exclude module: 'support-annotations'
|
||||
}
|
||||
androidTestImplementation('com.android.support.test:runner:0.5') {
|
||||
exclude module: 'support-annotations'
|
||||
}
|
||||
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
|
||||
androidTestImplementation 'org.hamcrest:hamcrest-library:1.3'
|
||||
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.6.0'
|
||||
androidTestImplementation group: 'commons-io', name: 'commons-io', version: '2.4'
|
||||
testImplementation 'org.mockito:mockito-core:1.10.19'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2', {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
}
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
||||
}
|
69
leanback/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-dontwarn com.squareup.okhttp.**
|
||||
|
||||
# Proguard configuration for Jackson 2.x (fasterxml package instead of codehaus package)
|
||||
-keep class com.fasterxml.jackson.databind.ObjectMapper {
|
||||
public <methods>;
|
||||
protected <methods>;
|
||||
}
|
||||
-keep class com.fasterxml.jackson.databind.ObjectWriter {
|
||||
public ** writeValueAsString(**);
|
||||
}
|
||||
-keepnames class com.fasterxml.jackson.** { *; }
|
||||
-dontwarn com.fasterxml.jackson.databind.**
|
||||
|
||||
|
||||
-keep class org.jetbrains.kotlin.** { *; }
|
||||
-keep class org.jetbrains.annotations.** { *; }
|
||||
-keepclassmembers class ** {
|
||||
@org.jetbrains.annotations.ReadOnly public *;
|
||||
}
|
||||
-keepattributes *Annotation*
|
||||
|
||||
-keep class kotlin.** { *; }
|
||||
-keep class org.jetbrains.** { *; }
|
||||
|
||||
# Platform used when running on Java 8 VMs. Will not be used at runtime.
|
||||
-dontwarn retrofit2.Platform$Java8
|
||||
# Retain generic type information for use by reflection by converters and adapters.
|
||||
-keepattributes Signature
|
||||
# Retain declared checked exceptions for use by a Proxy instance.
|
||||
-keepattributes Exceptions
|
||||
-dontwarn javax.annotation.**
|
||||
-dontwarn sun.misc.Unsafe
|
||||
-dontwarn okio.**
|
||||
-keep class de.nicidienase.chaosflix.common.entities.** { *; }
|
||||
#retrofit
|
||||
-dontwarn retrofit2.**
|
||||
-keep class retrofit2.** { *; }
|
||||
-keepattributes *Annotation*,Signature, Exceptions
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@retrofit2.http.* <methods>;
|
||||
}
|
||||
#endRetrofit
|
||||
|
||||
-keepclassmembers public class com.cypressworks.kotlinreflectionproguard.** {
|
||||
public *;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package de.nicidienase.chaosflix;
|
||||
|
||||
|
||||
import android.content.Intent;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.support.test.uiautomator.UiDevice;
|
||||
import android.test.suitebuilder.annotation.LargeTest;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import de.nicidienase.chaosflix.test.R;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.nicidienase.chaosflix.leanback.activities.ConferencesActivity;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
|
||||
import static android.support.test.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ConferencesActivityTest {
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<ConferencesActivity> mActivityTestRule
|
||||
= new ActivityTestRule<>(ConferencesActivity.class, false, false);
|
||||
private static MockWebServer server;
|
||||
private UiDevice mDevice;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException, TimeoutException {
|
||||
mDevice = UiDevice.getInstance(getInstrumentation());
|
||||
|
||||
server = new MockWebServer();
|
||||
server.start();
|
||||
String serverUrl = server.url("").toString();
|
||||
|
||||
server.enqueue(TestHelper.getResponseForRaw(R.raw.conferences_json));
|
||||
server.enqueue(TestHelper.getResponseForRaw(R.raw.conferences_101_33c3_json));
|
||||
|
||||
Intent i = new Intent();
|
||||
i.putExtra("server_url",serverUrl);
|
||||
mActivityTestRule.launchActivity(i);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conferencesActivityTest() throws InterruptedException {
|
||||
mDevice.pressDPadRight();
|
||||
mDevice.pressDPadRight();
|
||||
mDevice.pressDPadCenter();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package de.nicidienase.chaosflix;
|
||||
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.rule.ServiceTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import de.nicidienase.chaosflix.test.R;
|
||||
import de.nicidienase.chaosflix.common.network.MediaApiService;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
|
||||
import static junit.framework.Assert.fail;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
/**
|
||||
* Created by felix on 17.03.17.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class MediaApiServiceTest {
|
||||
|
||||
@ClassRule
|
||||
public static final ServiceTestRule mServiceRule = new ServiceTestRule();
|
||||
|
||||
private static final String TAG = MediaApiServiceTest.class.getSimpleName();
|
||||
private static MockWebServer server;
|
||||
private static String serverUrl;
|
||||
private static MediaApiService service;
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() throws IOException, TimeoutException {
|
||||
server = new MockWebServer();
|
||||
server.start();
|
||||
serverUrl = server.url("").toString();
|
||||
|
||||
Intent s = new Intent(InstrumentationRegistry.getTargetContext(),
|
||||
MediaApiService.class);
|
||||
s.putExtra(MediaApiService.RECORDING_URL,serverUrl);
|
||||
s.putExtra(MediaApiService.STREAMING_URL,serverUrl);
|
||||
IBinder binder = mServiceRule.bindService(s);
|
||||
service = ((MediaApiService.LocalBinder) binder).getService();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup(){
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getConferenceTest() throws IOException {
|
||||
server.enqueue(TestHelper.getResponseForRaw(R.raw.conferences_101_33c3_json));
|
||||
|
||||
service.getConference(101)
|
||||
.doOnError(throwable -> fail())
|
||||
.blockingSubscribe
|
||||
(conference -> assertThat(conference.getAcronym(),is("33c3")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sortAllEvents() throws IOException {
|
||||
server.enqueue(TestHelper.getResponseForRaw(R.raw.events_json));
|
||||
|
||||
service.getEvents()
|
||||
.doOnError(throwable -> fail())
|
||||
.blockingSubscribe(events -> Collections.sort(events));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getEventTest() throws IOException {
|
||||
server.enqueue(TestHelper.getResponseForRaw(R.raw.events_2837_json));
|
||||
|
||||
service.getEvent(2837)
|
||||
.doOnError(throwable -> fail())
|
||||
.blockingSubscribe(
|
||||
event -> assertThat(event.getGuid(), is("9f2e9ff0-1555-470b-8743-9f07f54e9097")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamingConferences() throws IOException {
|
||||
server.enqueue(TestHelper.getResponseForRaw(R.raw.streams_v2_json));
|
||||
|
||||
service.getStreamingConferences()
|
||||
.doOnError(throwable -> fail())
|
||||
.blockingSubscribe(liveConferences -> assertThat(1,is(liveConferences.size())));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() throws IOException {
|
||||
server.enqueue(TestHelper.getResponseForRaw(R.raw.streams_v2_json));
|
||||
|
||||
service.getStreamingConferences()
|
||||
.doOnError(throwable -> fail())
|
||||
.blockingSubscribe(liveConferences -> assertThat("FOSSGIS 2017",is(liveConferences.get(0).getConference())));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package de.nicidienase.chaosflix;
|
||||
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
|
||||
/**
|
||||
* Created by felix on 25.03.17.
|
||||
*/
|
||||
public class TestHelper {
|
||||
|
||||
static String getStringFromRaw(int resourceID) throws IOException {
|
||||
InputStream inputStream = InstrumentationRegistry
|
||||
.getContext().getResources().openRawResource(resourceID);
|
||||
StringWriter writer = new StringWriter();
|
||||
IOUtils.copy(inputStream, writer);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
static MockResponse getResponseForRaw(int resourceID) throws IOException {
|
||||
return new MockResponse().setBody(
|
||||
getStringFromRaw(resourceID));
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
{"acronym":"mrmcd101b","aspect_ratio":"4:3","updated_at":"2015-12-30T12:39:19.125+01:00","title":"mrmcd101b","schedule_url":null,"slug":"conferences/mrmcd/mrmcd101b","webgen_location":"conferences/mrmcd/mrmcd101b","logo_url":"https://static.media.ccc.de/media/conferences/mrmcd/mrmcd101b/folder-mrmcd-2006.png","images_url":"https://static.media.ccc.de/media/conferences/mrmcd/mrmcd101b","recordings_url":"http://cdn.media.ccc.de/events/mrmcd/mrmcd101b","url":"https://api.media.ccc.de/public/conferences/13","events":[]}
|
|
@ -0,0 +1 @@
|
|||
{"acronym":"18c3","aspect_ratio":"4:3","updated_at":"2015-12-30T12:39:22.463+01:00","title":"18C3: Hacking Is Not A Crime","schedule_url":null,"slug":"congress/2001","webgen_location":"congress/2001","logo_url":"https://static.media.ccc.de/media/congress/2001/folder-18c3.png","images_url":"https://static.media.ccc.de/media/congress/2001","recordings_url":"http://cdn.media.ccc.de/congress/2001","url":"https://api.media.ccc.de/public/conferences/21","events":[{"guid":"import-86dcc5d427d763434f","title":"Final","subtitle":null,"slug":"18c3_final_divx","link":"http://ccc.de","description":"\n","original_language":"eng","persons":[],"tags":["18c3"],"date":"2001-01-01T01:00:00.000+01:00","release_date":"2008-01-01","updated_at":"2017-03-24T22:45:05.035+01:00","length":259,"thumb_url":"https://static.media.ccc.de/media/congress/2001/creative/18c3_final_divx.jpg","poster_url":"https://static.media.ccc.de/media/congress/2001/creative/18c3_final_divx_preview.jpg","frontend_link":"https://media.ccc.de/v/18c3_final_divx","url":"https://api.media.ccc.de/public/events/709","conference_url":"https://api.media.ccc.de/public/conferences/21"},{"guid":"import-996ee6767784dc2e88","title":"Blinkenlights ISDN","subtitle":null,"slug":"Blinkenlights_isdn","link":"http://ccc.de","description":"\n","original_language":"eng","persons":[],"tags":["blinkenlights"," 18c3"],"date":"2001-01-01T01:00:00.000+01:00","release_date":"2008-01-01","updated_at":"2017-03-24T11:45:06.628+01:00","length":4771,"thumb_url":"https://static.media.ccc.de/media/congress/2001/video/vortraege/tag2/saal2/Blinkenlights_isdn.jpg","poster_url":"https://static.media.ccc.de/media/congress/2001/video/vortraege/tag2/saal2/Blinkenlights_isdn_preview.jpg","frontend_link":"https://media.ccc.de/v/Blinkenlights_isdn","url":"https://api.media.ccc.de/public/events/710","conference_url":"https://api.media.ccc.de/public/conferences/21"}]}
|
1
leanback/src/androidTest/res/raw/conferences_json
Normal file
1
leanback/src/androidTest/res/raw/events_2837_json
Normal file
1
leanback/src/androidTest/res/raw/events_2848_json
Normal file
1
leanback/src/androidTest/res/raw/events_3062_json
Normal file
|
@ -0,0 +1 @@
|
|||
{"guid":"f30baa0f-e541-4dc7-88cb-c6617efe659d","title":"From USA to DE.","subtitle":"A safe place for US hackers","slug":"gpn16-7677-from_usa_to_de","link":"https://entropia.de/GPN16:Fahrplan:XML?action=raw","description":"An overview of the possibilities US citisens have to live in Germany.\n","original_language":"eng","persons":["Sarah"],"tags":[],"date":"2016-05-26T20:00:00.000+02:00","release_date":"2016-05-27","updated_at":"2017-02-24T16:45:06.321+01:00","length":2267,"thumb_url":"https://static.media.ccc.de/media/events/gpn/gpn16/7677-hd.jpg","poster_url":"https://static.media.ccc.de/media/events/gpn/gpn16/7677-hd_preview.jpg","frontend_link":"https://media.ccc.de/v/gpn16-7677-from_usa_to_de","url":"https://api.media.ccc.de/public/events/3062","conference_url":"https://api.media.ccc.de/public/conferences/85","recordings":[{"size":28,"length":2261,"mime_type":"audio/opus","language":"eng","filename":"gpn16-7677-eng-From_USA_to_DE.opus","state":"new","folder":"opus","high_quality":false,"width":0,"height":0,"updated_at":"2016-05-29T02:35:04.442+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/opus/gpn16-7677-eng-From_USA_to_DE.opus","url":"https://api.media.ccc.de/public/recordings/10684","event_url":"https://api.media.ccc.de/public/events/3062","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":231,"length":2267,"mime_type":"video/mp4","language":"eng","filename":"gpn16-7677-eng-From_USA_to_DE_hd.mp4","state":"new","folder":"h264-hd","high_quality":true,"width":1920,"height":1080,"updated_at":"2016-05-28T21:26:18.554+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/h264-hd/gpn16-7677-eng-From_USA_to_DE_hd.mp4","url":"https://api.media.ccc.de/public/recordings/10618","event_url":"https://api.media.ccc.de/public/events/3062","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":34,"length":2261,"mime_type":"audio/mpeg","language":"eng","filename":"gpn16-7677-eng-From_USA_to_DE.mp3","state":"new","folder":"mp3","high_quality":false,"width":0,"height":0,"updated_at":"2016-05-29T02:35:30.027+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/mp3/gpn16-7677-eng-From_USA_to_DE.mp3","url":"https://api.media.ccc.de/public/recordings/10685","event_url":"https://api.media.ccc.de/public/events/3062","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":78,"length":2267,"mime_type":"video/webm","language":"eng","filename":"gpn16-7677-eng-From_USA_to_DE_webm-sd.webm","state":"new","folder":"webm-sd","high_quality":false,"width":720,"height":576,"updated_at":"2016-05-29T02:36:04.124+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/webm-sd/gpn16-7677-eng-From_USA_to_DE_webm-sd.webm","url":"https://api.media.ccc.de/public/recordings/10686","event_url":"https://api.media.ccc.de/public/events/3062","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":292,"length":2267,"mime_type":"video/webm","language":"eng","filename":"gpn16-7677-eng-From_USA_to_DE_webm-hd.webm","state":"new","folder":"webm-hd","high_quality":true,"width":1920,"height":1080,"updated_at":"2016-05-29T02:37:37.853+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/webm-hd/gpn16-7677-eng-From_USA_to_DE_webm-hd.webm","url":"https://api.media.ccc.de/public/recordings/10688","event_url":"https://api.media.ccc.de/public/events/3062","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":74,"length":2267,"mime_type":"video/mp4","language":"eng","filename":"gpn16-7677-eng-From_USA_to_DE_sd.mp4","state":"new","folder":"h264-sd","high_quality":false,"width":720,"height":576,"updated_at":"2016-05-29T02:38:17.634+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/h264-sd/gpn16-7677-eng-From_USA_to_DE_sd.mp4","url":"https://api.media.ccc.de/public/recordings/10689","event_url":"https://api.media.ccc.de/public/events/3062","conference_url":"https://api.media.ccc.de/public/conferences/85"}]}
|
1
leanback/src/androidTest/res/raw/events_3066_json
Normal file
|
@ -0,0 +1 @@
|
|||
{"guid":"7b59ca9e-7c40-4942-be90-2fd9acfea55b","title":"OSPAC","subtitle":"Open Source Podcast Audio Chain","slug":"gpn16-7675-ospac","link":"https://entropia.de/GPN16:Fahrplan:XML?action=raw","description":"Mit der Open-Source-Software Ospac kann man Mehrkanal-Aufnahmen für einen Podcast in veröffentlichbare Audio-Dateien abmischen. ","original_language":"deu","persons":["Sebastian Ritterbusch"],"tags":[],"date":"2016-05-26T21:30:00.000+02:00","release_date":"2016-05-27","updated_at":"2017-03-24T10:00:06.767+01:00","length":3306,"thumb_url":"https://static.media.ccc.de/media/events/gpn/gpn16/7675-hd.jpg","poster_url":"https://static.media.ccc.de/media/events/gpn/gpn16/7675-hd_preview.jpg","frontend_link":"https://media.ccc.de/v/gpn16-7675-ospac","url":"https://api.media.ccc.de/public/events/3066","conference_url":"https://api.media.ccc.de/public/conferences/85","recordings":[{"size":541,"length":3306,"mime_type":"video/mp4","language":"deu","filename":"gpn16-7675-deu-OSPAC_hd.mp4","state":"new","folder":"h264-hd","high_quality":true,"width":1920,"height":1080,"updated_at":"2016-05-27T02:28:51.224+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/h264-hd/gpn16-7675-deu-OSPAC_hd.mp4","url":"https://api.media.ccc.de/public/recordings/10477","event_url":"https://api.media.ccc.de/public/events/3066","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":159,"length":3306,"mime_type":"video/webm","language":"deu","filename":"gpn16-7675-deu-OSPAC_webm-sd.webm","state":"new","folder":"webm-sd","high_quality":false,"width":720,"height":576,"updated_at":"2016-05-27T14:17:36.490+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/webm-sd/gpn16-7675-deu-OSPAC_webm-sd.webm","url":"https://api.media.ccc.de/public/recordings/10486","event_url":"https://api.media.ccc.de/public/events/3066","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":50,"length":3300,"mime_type":"audio/mpeg","language":"deu","filename":"gpn16-7675-deu-OSPAC.mp3","state":"new","folder":"mp3","high_quality":false,"width":0,"height":0,"updated_at":"2016-05-27T14:18:08.501+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/mp3/gpn16-7675-deu-OSPAC.mp3","url":"https://api.media.ccc.de/public/recordings/10487","event_url":"https://api.media.ccc.de/public/events/3066","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":42,"length":3300,"mime_type":"audio/opus","language":"deu","filename":"gpn16-7675-deu-OSPAC.opus","state":"new","folder":"opus","high_quality":false,"width":0,"height":0,"updated_at":"2016-05-27T14:14:24.605+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/opus/gpn16-7675-deu-OSPAC.opus","url":"https://api.media.ccc.de/public/recordings/10484","event_url":"https://api.media.ccc.de/public/events/3066","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":145,"length":3306,"mime_type":"video/mp4","language":"deu","filename":"gpn16-7675-deu-OSPAC_sd.mp4","state":"new","folder":"h264-sd","high_quality":false,"width":720,"height":576,"updated_at":"2016-05-27T14:16:07.606+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/h264-sd/gpn16-7675-deu-OSPAC_sd.mp4","url":"https://api.media.ccc.de/public/recordings/10485","event_url":"https://api.media.ccc.de/public/events/3066","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":507,"length":3306,"mime_type":"video/webm","language":"deu","filename":"gpn16-7675-deu-OSPAC_webm-hd.webm","state":"new","folder":"webm-hd","high_quality":true,"width":1920,"height":1080,"updated_at":"2016-05-27T14:13:52.563+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/webm-hd/gpn16-7675-deu-OSPAC_webm-hd.webm","url":"https://api.media.ccc.de/public/recordings/10483","event_url":"https://api.media.ccc.de/public/events/3066","conference_url":"https://api.media.ccc.de/public/conferences/85"}]}
|
1
leanback/src/androidTest/res/raw/events_3104_json
Normal file
|
@ -0,0 +1 @@
|
|||
{"guid":"b2bc162c-1745-4f45-ba56-766b4174095b","title":"Run your own fucking infrastructure","subtitle":"Why you should consider running your own shit","slug":"gpn16-7582-run_your_own_fucking_infrastructure","link":"https://entropia.de/GPN16:Fahrplan:XML?action=raw","description":null,"original_language":"deu","persons":["mc.fly"],"tags":[],"date":"2016-05-29T13:15:00.000+02:00","release_date":"2016-05-29","updated_at":"2017-03-24T14:45:04.540+01:00","length":3505,"thumb_url":"https://static.media.ccc.de/media/events/gpn/gpn16/7582-hd.jpg","poster_url":"https://static.media.ccc.de/media/events/gpn/gpn16/7582-hd_preview.jpg","frontend_link":"https://media.ccc.de/v/gpn16-7582-run_your_own_fucking_infrastructure","url":"https://api.media.ccc.de/public/events/3104","conference_url":"https://api.media.ccc.de/public/conferences/85","recordings":[{"size":590,"length":3505,"mime_type":"video/mp4","language":"deu","filename":"gpn16-7582-deu-Run_your_own_fucking_infrastructure_hd.mp4","state":"new","folder":"h264-hd","high_quality":true,"width":1920,"height":1080,"updated_at":"2016-05-29T16:19:48.040+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/h264-hd/gpn16-7582-deu-Run_your_own_fucking_infrastructure_hd.mp4","url":"https://api.media.ccc.de/public/recordings/10726","event_url":"https://api.media.ccc.de/public/events/3104","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":144,"length":3505,"mime_type":"video/webm","language":"deu","filename":"gpn16-7582-deu-Run_your_own_fucking_infrastructure_webm-sd.webm","state":"new","folder":"webm-sd","high_quality":false,"width":720,"height":576,"updated_at":"2016-06-02T20:28:37.637+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/webm-sd/gpn16-7582-deu-Run_your_own_fucking_infrastructure_webm-sd.webm","url":"https://api.media.ccc.de/public/recordings/10736","event_url":"https://api.media.ccc.de/public/events/3104","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":537,"length":3505,"mime_type":"video/webm","language":"deu","filename":"gpn16-7582-deu-Run_your_own_fucking_infrastructure_webm-hd.webm","state":"new","folder":"webm-hd","high_quality":true,"width":1920,"height":1080,"updated_at":"2016-06-02T20:33:18.238+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/webm-hd/gpn16-7582-deu-Run_your_own_fucking_infrastructure_webm-hd.webm","url":"https://api.media.ccc.de/public/recordings/10738","event_url":"https://api.media.ccc.de/public/events/3104","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":53,"length":3499,"mime_type":"audio/mpeg","language":"deu","filename":"gpn16-7582-deu-Run_your_own_fucking_infrastructure.mp3","state":"new","folder":"mp3","high_quality":false,"width":0,"height":0,"updated_at":"2016-05-29T16:26:01.895+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/mp3/gpn16-7582-deu-Run_your_own_fucking_infrastructure.mp3","url":"https://api.media.ccc.de/public/recordings/10728","event_url":"https://api.media.ccc.de/public/events/3104","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":136,"length":3505,"mime_type":"video/mp4","language":"deu","filename":"gpn16-7582-deu-Run_your_own_fucking_infrastructure_sd.mp4","state":"new","folder":"h264-sd","high_quality":false,"width":720,"height":576,"updated_at":"2016-05-29T16:31:11.730+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/h264-sd/gpn16-7582-deu-Run_your_own_fucking_infrastructure_sd.mp4","url":"https://api.media.ccc.de/public/recordings/10730","event_url":"https://api.media.ccc.de/public/events/3104","conference_url":"https://api.media.ccc.de/public/conferences/85"},{"size":47,"length":3499,"mime_type":"audio/opus","language":"deu","filename":"gpn16-7582-deu-Run_your_own_fucking_infrastructure.opus","state":"new","folder":"opus","high_quality":false,"width":0,"height":0,"updated_at":"2016-05-29T16:26:33.792+02:00","recording_url":"http://cdn.media.ccc.de/events/gpn/gpn16/opus/gpn16-7582-deu-Run_your_own_fucking_infrastructure.opus","url":"https://api.media.ccc.de/public/recordings/10729","event_url":"https://api.media.ccc.de/public/events/3104","conference_url":"https://api.media.ccc.de/public/conferences/85"}]}
|
1
leanback/src/androidTest/res/raw/events_3623_json
Normal file
1
leanback/src/androidTest/res/raw/events_3668_json
Normal file
1
leanback/src/androidTest/res/raw/events_3768_json
Normal file
1
leanback/src/androidTest/res/raw/events_709_json
Normal file
|
@ -0,0 +1 @@
|
|||
{"guid":"import-86dcc5d427d763434f","title":"Final","subtitle":null,"slug":"18c3_final_divx","link":"http://ccc.de","description":"\n","original_language":"eng","persons":[],"tags":["18c3"],"date":"2001-01-01T01:00:00.000+01:00","release_date":"2008-01-01","updated_at":"2017-03-24T22:45:05.035+01:00","length":259,"thumb_url":"https://static.media.ccc.de/media/congress/2001/creative/18c3_final_divx.jpg","poster_url":"https://static.media.ccc.de/media/congress/2001/creative/18c3_final_divx_preview.jpg","frontend_link":"https://media.ccc.de/v/18c3_final_divx","url":"https://api.media.ccc.de/public/events/709","conference_url":"https://api.media.ccc.de/public/conferences/21","recordings":[{"size":null,"length":259,"mime_type":"video/mp4","language":"eng","filename":"18c3_final_divx.mp4","state":"downloaded","folder":"mp4","high_quality":true,"width":360,"height":280,"updated_at":"2016-01-27T11:03:09.279+01:00","recording_url":"http://cdn.media.ccc.de/congress/2001/mp4/18c3_final_divx.mp4","url":"https://api.media.ccc.de/public/recordings/4207","event_url":"https://api.media.ccc.de/public/events/709","conference_url":"https://api.media.ccc.de/public/conferences/21"}]}
|
1
leanback/src/androidTest/res/raw/events_710_json
Normal file
|
@ -0,0 +1 @@
|
|||
{"guid":"import-996ee6767784dc2e88","title":"Blinkenlights ISDN","subtitle":null,"slug":"Blinkenlights_isdn","link":"http://ccc.de","description":"\n","original_language":"eng","persons":[],"tags":["blinkenlights"," 18c3"],"date":"2001-01-01T01:00:00.000+01:00","release_date":"2008-01-01","updated_at":"2017-03-24T11:45:06.628+01:00","length":4771,"thumb_url":"https://static.media.ccc.de/media/congress/2001/video/vortraege/tag2/saal2/Blinkenlights_isdn.jpg","poster_url":"https://static.media.ccc.de/media/congress/2001/video/vortraege/tag2/saal2/Blinkenlights_isdn_preview.jpg","frontend_link":"https://media.ccc.de/v/Blinkenlights_isdn","url":"https://api.media.ccc.de/public/events/710","conference_url":"https://api.media.ccc.de/public/conferences/21","recordings":[{"size":null,"length":4771,"mime_type":"video/mp4","language":"eng","filename":"Blinkenlights_isdn.mp4","state":"downloaded","folder":"video/vortraege/tag2/saal2","high_quality":false,"width":192,"height":144,"updated_at":"2016-02-03T14:06:10.016+01:00","recording_url":"http://cdn.media.ccc.de/congress/2001/video/vortraege/tag2/saal2/Blinkenlights_isdn.mp4","url":"https://api.media.ccc.de/public/recordings/4303","event_url":"https://api.media.ccc.de/public/events/710","conference_url":"https://api.media.ccc.de/public/conferences/21"}]}
|
1
leanback/src/androidTest/res/raw/events_json
Normal file
948
leanback/src/androidTest/res/raw/example_streams_v1_json
Normal file
|
@ -0,0 +1,948 @@
|
|||
[
|
||||
{
|
||||
"conference": "32C3",
|
||||
"group": "Lecture Rooms",
|
||||
"rooms": [
|
||||
{
|
||||
"slug": "hall1",
|
||||
"schedulename": "Hall 1",
|
||||
"thumb": "https://streaming.media.ccc.de/32c3/../thumbs/s1.png",
|
||||
"link": "https://streaming.media.ccc.de/32c3/hall1/",
|
||||
"display": "Hall 1",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "hd-native",
|
||||
"display": "Hall 1 FullHD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 2.8 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s1_native_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s1_native_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "hd-translated",
|
||||
"display": "Hall 1 FullHD Video (Translation)",
|
||||
"type": "video",
|
||||
"isTranslated": true,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 2.8 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s1_translated_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s1_translated_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-native",
|
||||
"display": "Hall 1 SD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s1_native_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s1_native_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-translated",
|
||||
"display": "Hall 1 SD Video (Translation)",
|
||||
"type": "video",
|
||||
"isTranslated": true,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s1_translated_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s1_translated_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "slides-native",
|
||||
"display": "Hall 1 Slides",
|
||||
"type": "slides",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s1_native_slides.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s1_native_slides.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "slides-translated",
|
||||
"display": "Hall 1 Slides (Translation)",
|
||||
"type": "slides",
|
||||
"isTranslated": true,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s1_translated_slides.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s1_translated_slides.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-native",
|
||||
"display": "Hall 1 Audio",
|
||||
"type": "audio",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s1_native.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s1_native.opus"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-translated",
|
||||
"display": "Hall 1 Audio (Translation)",
|
||||
"type": "audio",
|
||||
"isTranslated": true,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s1_translated.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s1_translated.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "hall2",
|
||||
"schedulename": "Hall 2",
|
||||
"thumb": "https://streaming.media.ccc.de/32c3/../thumbs/s2.png",
|
||||
"link": "https://streaming.media.ccc.de/32c3/hall2/",
|
||||
"display": "Hall 2",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "hd-native",
|
||||
"display": "Hall 2 FullHD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 2.8 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s2_native_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s2_native_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "hd-translated",
|
||||
"display": "Hall 2 FullHD Video (Translation)",
|
||||
"type": "video",
|
||||
"isTranslated": true,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 2.8 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s2_translated_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s2_translated_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-native",
|
||||
"display": "Hall 2 SD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s2_native_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s2_native_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-translated",
|
||||
"display": "Hall 2 SD Video (Translation)",
|
||||
"type": "video",
|
||||
"isTranslated": true,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s2_translated_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s2_translated_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "slides-native",
|
||||
"display": "Hall 2 Slides",
|
||||
"type": "slides",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s2_native_slides.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s2_native_slides.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "slides-translated",
|
||||
"display": "Hall 2 Slides (Translation)",
|
||||
"type": "slides",
|
||||
"isTranslated": true,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s2_translated_slides.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s2_translated_slides.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-native",
|
||||
"display": "Hall 2 Audio",
|
||||
"type": "audio",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s2_native.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s2_native.opus"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-translated",
|
||||
"display": "Hall 2 Audio (Translation)",
|
||||
"type": "audio",
|
||||
"isTranslated": true,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s2_translated.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s2_translated.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "hallg",
|
||||
"schedulename": "Hall G",
|
||||
"thumb": "https://streaming.media.ccc.de/32c3/../thumbs/s3.png",
|
||||
"link": "https://streaming.media.ccc.de/32c3/hallg/",
|
||||
"display": "Hall G",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "hd-native",
|
||||
"display": "Hall G FullHD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 2.8 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s3_native_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s3_native_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "hd-translated",
|
||||
"display": "Hall G FullHD Video (Translation)",
|
||||
"type": "video",
|
||||
"isTranslated": true,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 2.8 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s3_translated_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s3_translated_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-native",
|
||||
"display": "Hall G SD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s3_native_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s3_native_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-translated",
|
||||
"display": "Hall G SD Video (Translation)",
|
||||
"type": "video",
|
||||
"isTranslated": true,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s3_translated_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s3_translated_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "slides-native",
|
||||
"display": "Hall G Slides",
|
||||
"type": "slides",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s3_native_slides.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s3_native_slides.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "slides-translated",
|
||||
"display": "Hall G Slides (Translation)",
|
||||
"type": "slides",
|
||||
"isTranslated": true,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s3_translated_slides.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s3_translated_slides.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-native",
|
||||
"display": "Hall G Audio",
|
||||
"type": "audio",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s3_native.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s3_native.opus"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-translated",
|
||||
"display": "Hall G Audio (Translation)",
|
||||
"type": "audio",
|
||||
"isTranslated": true,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s3_translated.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s3_translated.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "hall6",
|
||||
"schedulename": "Hall 6",
|
||||
"thumb": "https://streaming.media.ccc.de/32c3/../thumbs/s4.png",
|
||||
"link": "https://streaming.media.ccc.de/32c3/hall6/",
|
||||
"display": "Hall 6",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "hd-native",
|
||||
"display": "Hall 6 FullHD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 2.8 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s4_native_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "hd-translated",
|
||||
"display": "Hall 6 FullHD Video (Translation)",
|
||||
"type": "video",
|
||||
"isTranslated": true,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 2.8 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_translated_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s4_translated_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-native",
|
||||
"display": "Hall 6 SD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s4_native_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-translated",
|
||||
"display": "Hall 6 SD Video (Translation)",
|
||||
"type": "video",
|
||||
"isTranslated": true,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_translated_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s4_translated_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "slides-native",
|
||||
"display": "Hall 6 Slides",
|
||||
"type": "slides",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native_slides.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s4_native_slides.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "slides-translated",
|
||||
"display": "Hall 6 Slides (Translation)",
|
||||
"type": "slides",
|
||||
"isTranslated": true,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_translated_slides.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, XXX kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s4_translated_slides.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-native",
|
||||
"display": "Hall 6 Audio",
|
||||
"type": "audio",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native.opus"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-translated",
|
||||
"display": "Hall 6 Audio (Translation)",
|
||||
"type": "audio",
|
||||
"isTranslated": true,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_translated.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_translated.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"conference": "32C3",
|
||||
"group": "Live Music",
|
||||
"rooms": [
|
||||
{
|
||||
"slug": "lounge",
|
||||
"schedulename": "lounge",
|
||||
"thumb": "https://streaming.media.ccc.de/32c3/../thumbs/lounge.png",
|
||||
"link": "https://streaming.media.ccc.de/32c3/lounge/",
|
||||
"display": "Lounge",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "music-native",
|
||||
"display": "Lounge Radio",
|
||||
"type": "music",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 192 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/lounge.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/lounge.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "ambient",
|
||||
"schedulename": "ambient",
|
||||
"thumb": "https://streaming.media.ccc.de/32c3/../thumbs/ambient.png",
|
||||
"link": "https://streaming.media.ccc.de/32c3/ambient/",
|
||||
"display": "Ambient",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "music-native",
|
||||
"display": "Ambient Radio",
|
||||
"type": "music",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 192 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/ambient.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/ambient.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "dome",
|
||||
"schedulename": "dome",
|
||||
"thumb": "https://streaming.media.ccc.de/32c3/../thumbs/dome.png",
|
||||
"link": "https://streaming.media.ccc.de/32c3/dome/",
|
||||
"display": "Dome",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "music-native",
|
||||
"display": "Dome Radio",
|
||||
"type": "music",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 192 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/dome.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/dome.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "chaos-west",
|
||||
"schedulename": "chaos-west",
|
||||
"thumb": "https://streaming.media.ccc.de/32c3/../thumbs/chaos-west.png",
|
||||
"link": "https://streaming.media.ccc.de/32c3/chaos-west/",
|
||||
"display": "Chaos-West",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "music-native",
|
||||
"display": "Chaos-West Radio",
|
||||
"type": "music",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 192 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/chaos-west.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/chaos-west.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"conference": "32C3",
|
||||
"group": "Live Podcasts",
|
||||
"rooms": [
|
||||
{
|
||||
"slug": "sendezentrum",
|
||||
"schedulename": "Bühne",
|
||||
"thumb": "https://streaming.media.ccc.de/32c3/../thumbs/s5.png",
|
||||
"link": "https://streaming.media.ccc.de/32c3/sendezentrum/",
|
||||
"display": "Sendezentrum",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "hd-stereo",
|
||||
"display": "Sendezentrum FullHD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 2.8 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s5_stereo_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s5_stereo_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-stereo",
|
||||
"display": "Sendezentrum SD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s5_stereo_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s5_stereo_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-stereo",
|
||||
"display": "Sendezentrum Audio",
|
||||
"type": "audio",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s5_stereo.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s5_stereo.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "podcastertisch",
|
||||
"schedulename": "Podcaster-Tisch",
|
||||
"thumb": "https://streaming.media.ccc.de/32c3/../thumbs/podcastertisch.png",
|
||||
"link": "https://streaming.media.ccc.de/32c3/podcastertisch/",
|
||||
"display": "Sendezentrum - Podcastertisch",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "music-native",
|
||||
"display": "Sendezentrum - Podcastertisch Radio",
|
||||
"type": "music",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 192 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/podcastertisch.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/podcastertisch.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
186
leanback/src/androidTest/res/raw/streams_v1_2_json
Normal file
|
@ -0,0 +1,186 @@
|
|||
[
|
||||
{
|
||||
"conference": "Easterhegg 2017",
|
||||
"group": "Für die Glupscher",
|
||||
"rooms": [
|
||||
{
|
||||
"slug": "vortragssaal",
|
||||
"schedulename": "Vortragssaal",
|
||||
"thumb": "http://streaming.media.ccc.de/thumbs/s6.png",
|
||||
"link": "http://streaming.media.ccc.de/eh17/vortragssaal",
|
||||
"display": "Vortragssaal",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "hd-native",
|
||||
"display": "Vortragssaal FullHD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 3.5 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s6_native_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s6_native_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-native",
|
||||
"display": "Vortragssaal SD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 1 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s6_native_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s6_native_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-native",
|
||||
"display": "Vortragssaal Audio",
|
||||
"type": "audio",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s6_native.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s6_native.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "grosses-kolleg",
|
||||
"schedulename": "Großes Kolleg",
|
||||
"thumb": "http://streaming.media.ccc.de/thumbs/s4.png",
|
||||
"link": "http://streaming.media.ccc.de/eh17/grosses-kolleg",
|
||||
"display": "Großes Kolleg",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "hd-native",
|
||||
"display": "Großes Kolleg FullHD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 3.5 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s4_native_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-native",
|
||||
"display": "Großes Kolleg SD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 1 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s4_native_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-native",
|
||||
"display": "Großes Kolleg Audio",
|
||||
"type": "audio",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"conference": "Easterhegg 2017",
|
||||
"group": "Auf die Lauscher",
|
||||
"rooms": [
|
||||
{
|
||||
"slug": "lounge",
|
||||
"schedulename": "Lounge",
|
||||
"thumb": "http://streaming.media.ccc.de/thumbs/lounge.png",
|
||||
"link": "http://streaming.media.ccc.de/eh17/lounge",
|
||||
"display": "Lounge",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "music-native",
|
||||
"display": "Lounge Radio",
|
||||
"type": "music",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 192 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/lounge.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/lounge.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
195
leanback/src/androidTest/res/raw/streams_v2_2_json
Normal file
|
@ -0,0 +1,195 @@
|
|||
[
|
||||
{
|
||||
"conference": "Easterhegg 2017",
|
||||
"slug": "eh17",
|
||||
"author": "Easterhegg, you know!",
|
||||
"description": "Ist das Zufall oder kann das weg?",
|
||||
"keywords": "Easterhegg, Ostern, Hack, CCC, Zufall, Mühlheim, Video, Streaming, Live, Livestream",
|
||||
"startsAt": "2017-04-14T11:30:00+0000",
|
||||
"endsAt": "2017-04-17T14:30:00+0000",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Für die Glupscher",
|
||||
"rooms": [
|
||||
{
|
||||
"slug": "vortragssaal",
|
||||
"schedulename": "Vortragssaal",
|
||||
"thumb": "http://streaming.media.ccc.de/thumbs/s6.png",
|
||||
"link": "http://streaming.media.ccc.de/eh17/vortragssaal",
|
||||
"display": "Vortragssaal",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "hd-native",
|
||||
"display": "Vortragssaal FullHD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 3.5 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s6_native_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s6_native_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-native",
|
||||
"display": "Vortragssaal SD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 1 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s6_native_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s6_native_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-native",
|
||||
"display": "Vortragssaal Audio",
|
||||
"type": "audio",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s6_native.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s6_native.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "grosses-kolleg",
|
||||
"schedulename": "Großes Kolleg",
|
||||
"thumb": "http://streaming.media.ccc.de/thumbs/s4.png",
|
||||
"link": "http://streaming.media.ccc.de/eh17/grosses-kolleg",
|
||||
"display": "Großes Kolleg",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "hd-native",
|
||||
"display": "Großes Kolleg FullHD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 3.5 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native_hd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s4_native_hd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-native",
|
||||
"display": "Großes Kolleg SD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 1 MBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native_sd.webm"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/hls/s4_native_sd.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-native",
|
||||
"display": "Großes Kolleg Audio",
|
||||
"type": "audio",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/s4_native.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Auf die Lauscher",
|
||||
"rooms": [
|
||||
{
|
||||
"slug": "lounge",
|
||||
"schedulename": "Lounge",
|
||||
"thumb": "http://streaming.media.ccc.de/thumbs/lounge.png",
|
||||
"link": "http://streaming.media.ccc.de/eh17/lounge",
|
||||
"display": "Lounge",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "music-native",
|
||||
"display": "Lounge Radio",
|
||||
"type": "music",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 192 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/lounge.mp3"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 96 kBit/s",
|
||||
"url": "http://cdn.c3voc.de/lounge.opus"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
234
leanback/src/androidTest/res/raw/streams_v2_json
Normal file
|
@ -0,0 +1,234 @@
|
|||
[
|
||||
{
|
||||
"conference": "FOSSGIS 2017",
|
||||
"slug": "fossgis17",
|
||||
"author": "FOSSGIS e.V.",
|
||||
"description": "Die FOSSGIS-Konferenz ist im D-A-CH Raum die führende Konferenz für Freie und Open Source Software für Geoinformationssysteme OpenStreetMap.",
|
||||
"keywords": "FOSSGIS, FOSSGIS-Konferenz, 2017, FOSSGIS-Konferenz 2017, Open Source, GIS, Konferenz, Geoinformatik, OpenStreetMap, Passau, Video, Streaming, Live, Livestream",
|
||||
"startsAt": "2017-03-22T08:30:00+0000",
|
||||
"endsAt": "2017-03-25T14:00:00+0000",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Lecture Rooms",
|
||||
"rooms": [
|
||||
{
|
||||
"slug": "AM-HS-9",
|
||||
"schedulename": "AM HS 9",
|
||||
"thumb": "https://streaming.media.ccc.de/thumbs/s6.png",
|
||||
"link": "https://streaming.media.ccc.de/fossgis17/AM-HS-9",
|
||||
"display": "AM HS 9",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "hd-native",
|
||||
"display": "AM HS 9 FullHD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 3.5 MBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-native",
|
||||
"display": "AM HS 9 SD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 1 MBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-native",
|
||||
"display": "AM HS 9 Audio",
|
||||
"type": "audio",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "IM-HS-11",
|
||||
"schedulename": "IM HS 11",
|
||||
"thumb": "https://streaming.media.ccc.de/thumbs/s4.png",
|
||||
"link": "https://streaming.media.ccc.de/fossgis17/IM-HS-11",
|
||||
"display": "IM-HS-11",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "hd-native",
|
||||
"display": "IM-HS-11 FullHD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 3.5 MBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-native",
|
||||
"display": "IM-HS-11 SD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 1 MBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-native",
|
||||
"display": "IM-HS-11 Audio",
|
||||
"type": "audio",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "IM-HS-13",
|
||||
"schedulename": "IM HS 13",
|
||||
"thumb": "https://streaming.media.ccc.de/thumbs/s5.png",
|
||||
"link": "https://streaming.media.ccc.de/fossgis17/IM-HS-13",
|
||||
"display": "IM HS 13",
|
||||
"streams": [
|
||||
{
|
||||
"slug": "hd-native",
|
||||
"display": "IM HS 13 FullHD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1920,
|
||||
1080
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1920x1080, VP8+Vorbis in WebM, 3.5 MBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1920x1080, h264+AAC im MPEG-TS-Container via HTTP, 3 MBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "sd-native",
|
||||
"display": "IM HS 13 SD Video",
|
||||
"type": "video",
|
||||
"isTranslated": false,
|
||||
"videoSize": [
|
||||
1024,
|
||||
576
|
||||
],
|
||||
"urls": {
|
||||
"webm": {
|
||||
"display": "WebM",
|
||||
"tech": "1024x576, VP8+Vorbis in WebM, 1 MBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
},
|
||||
"hls": {
|
||||
"display": "HLS",
|
||||
"tech": "1024x576, h264+AAC im MPEG-TS-Container via HTTP, 800 kBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"slug": "audio-native",
|
||||
"display": "IM HS 13 Audio",
|
||||
"type": "audio",
|
||||
"isTranslated": false,
|
||||
"videoSize": null,
|
||||
"urls": {
|
||||
"mp3": {
|
||||
"display": "MP3",
|
||||
"tech": "MP3-Audio, 96 kBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
},
|
||||
"opus": {
|
||||
"display": "Opus",
|
||||
"tech": "Opus-Audio, 64 kBit/s",
|
||||
"url": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
44
leanback/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="de.nicidienase.chaosflix"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false"/>
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="true"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/icon_notext_144x144"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:name="com.orm.SugarApp"
|
||||
android:theme="@style/Theme.Leanback">
|
||||
<activity
|
||||
android:name=".leanback.activities.ConferencesActivity"
|
||||
android:banner="@drawable/banner"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="@string/app_name"
|
||||
android:logo="@drawable/icon"
|
||||
android:screenOrientation="landscape">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".leanback.activities.EventsActivity"
|
||||
android:exported="true"/>
|
||||
<activity android:name=".leanback.activities.EventDetailsActivity"
|
||||
android:exported="true"/>
|
||||
<activity android:name=".leanback.activities.DetailsActivity"
|
||||
android:exported="true"/>
|
||||
<activity android:name=".leanback.activities.PlayerActivity"/>
|
||||
<service android:name=".common.network.MediaApiService"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.nicidienase.chaosflix.leanback;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v17.leanback.widget.ImageCardView;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
|
||||
import de.nicidienase.chaosflix.R;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Conference;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Event;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.LiveConference;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.Room;
|
||||
|
||||
/*
|
||||
* A CardPresenter is used to generate Views and bind Objects to them on demand.
|
||||
* It contains an Image CardView
|
||||
*/
|
||||
public class CardPresenter extends Presenter {
|
||||
private static final String TAG = "CardPresenter";
|
||||
|
||||
private static final int CARD_WIDTH = 313;
|
||||
private static final int CARD_HEIGHT_4 = 235;
|
||||
private static final int CARD_HEIGHT_16 = 177;
|
||||
private static int sSelectedBackgroundColor;
|
||||
private static int sDefaultBackgroundColor;
|
||||
private Drawable mDefaultCardImage;
|
||||
|
||||
private static void updateCardBackgroundColor(ImageCardView view, boolean selected) {
|
||||
int color = selected ? sSelectedBackgroundColor : sDefaultBackgroundColor;
|
||||
// Both background colors should be set because the view's background is temporarily visible
|
||||
// during animations.
|
||||
view.setBackgroundColor(color);
|
||||
view.findViewById(R.id.info_field).setBackgroundColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
sDefaultBackgroundColor = parent.getResources().getColor(R.color.default_background);
|
||||
sSelectedBackgroundColor = parent.getResources().getColor(R.color.selected_background);
|
||||
mDefaultCardImage = parent.getResources().getDrawable(R.drawable.default_background);
|
||||
|
||||
ImageCardView cardView = new ImageCardView(parent.getContext()) {
|
||||
@Override
|
||||
public void setSelected(boolean selected) {
|
||||
updateCardBackgroundColor(this, selected);
|
||||
super.setSelected(selected);
|
||||
}
|
||||
};
|
||||
|
||||
cardView.setFocusable(true);
|
||||
cardView.setFocusableInTouchMode(true);
|
||||
updateCardBackgroundColor(cardView, false);
|
||||
return new ViewHolder(cardView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
|
||||
ImageCardView cardView = (ImageCardView) viewHolder.view;
|
||||
cardView.setMainImageScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
if (item instanceof Conference) {
|
||||
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT_4);
|
||||
Conference conference = (Conference) item;
|
||||
cardView.setTitleText(conference.getTitle());
|
||||
cardView.setContentText(conference.getAcronym());
|
||||
if (conference.getLogoUrl() != null) {
|
||||
|
||||
Glide.with(viewHolder.view.getContext())
|
||||
.load(conference.getLogoUrl())
|
||||
.fitCenter()
|
||||
.error(mDefaultCardImage)
|
||||
.into(cardView.getMainImageView());
|
||||
}
|
||||
} else if (item instanceof Event) {
|
||||
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT_16);
|
||||
Event event = (Event) item;
|
||||
cardView.setTitleText(event.getTitle());
|
||||
cardView.setContentText(event.getSubtitle());
|
||||
// cardView.setContentText(android.text.TextUtils.join(", ",event.getPersons()));
|
||||
if (event.getThumbUrl() != null) {
|
||||
Glide.with(viewHolder.view.getContext())
|
||||
.load(event.getThumbUrl())
|
||||
.fitCenter()
|
||||
.error(mDefaultCardImage)
|
||||
.into(cardView.getMainImageView());
|
||||
}
|
||||
} else if (item instanceof LiveConference) {
|
||||
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT_4);
|
||||
LiveConference con = (LiveConference) item;
|
||||
cardView.setTitleText(con.getConference());
|
||||
cardView.setMainImage(mDefaultCardImage);
|
||||
cardView.setContentText(con.getDescription());
|
||||
} else if (item instanceof Room) {
|
||||
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT_16);
|
||||
Room room = (Room) item;
|
||||
cardView.setTitleText(room.getDisplay());
|
||||
cardView.setContentText(room.getShedulename());
|
||||
if (room.getThumb() != null) {
|
||||
Glide.with(viewHolder.view.getContext())
|
||||
.load(room.getThumb())
|
||||
.fitCenter()
|
||||
.error(mDefaultCardImage)
|
||||
.into(cardView.getMainImageView());
|
||||
}
|
||||
} else if(item instanceof String) {
|
||||
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT_4);
|
||||
cardView.setTitleText((String) item);
|
||||
Glide.with(viewHolder.view.getContext())
|
||||
.load(R.drawable.icon)
|
||||
.into(cardView.getMainImageView());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
|
||||
ImageCardView cardView = (ImageCardView) viewHolder.view;
|
||||
// Remove references to images so that the garbage collector can free up memory
|
||||
cardView.setBadgeImage(null);
|
||||
cardView.setMainImage(null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package de.nicidienase.chaosflix.leanback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
import de.nicidienase.chaosflix.R;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Event;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.Room;
|
||||
|
||||
/**
|
||||
* Created by felix on 18.03.17.
|
||||
*/
|
||||
|
||||
public class EventDetailsDescriptionPresenter extends Presenter {
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public EventDetailsDescriptionPresenter(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
View view = LayoutInflater.from(mContext).inflate(R.layout.detail_view_content, null);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder vh, Object item) {
|
||||
TextView titleText = ButterKnife.findById(vh.view, R.id.title_text);
|
||||
TextView speakersText = ButterKnife.findById(vh.view, R.id.speakers_text);
|
||||
TextView subtitleText = ButterKnife.findById(vh.view, R.id.subtitle_text);
|
||||
TextView descriptionText = ButterKnife.findById(vh.view, R.id.description_text);
|
||||
if (item instanceof Event) {
|
||||
Event event = (Event) item;
|
||||
|
||||
titleText.setText(event.getTitle());
|
||||
subtitleText.setText(event.getSubtitle());
|
||||
String speaker = TextUtils.join(", ", event.getPersons());
|
||||
speakersText.setText(speaker);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(event.getDescription())
|
||||
.append("\n")
|
||||
.append("\nreleased at: ").append(event.getReleaseDate())
|
||||
.append("\nTags: ").append(android.text.TextUtils.join(", ", event.getTags()));
|
||||
descriptionText.setText(sb.toString());
|
||||
} else if (item instanceof Room) {
|
||||
Room room = (Room) item;
|
||||
titleText.setText(room.getDisplay());
|
||||
subtitleText.setText(room.getShedulename());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnbindViewHolder(ViewHolder viewHolder) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package de.nicidienase.chaosflix.leanback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.support.v17.leanback.widget.RowHeaderPresenter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import de.nicidienase.chaosflix.R;
|
||||
|
||||
/**
|
||||
* Created by felix on 16.04.17.
|
||||
*/
|
||||
|
||||
public class HeaderItemPresenter extends RowHeaderPresenter {
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
LayoutInflater inflater = (LayoutInflater) parent.getContext()
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View view = inflater.inflate(R.layout.header_item_layout, null);
|
||||
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
|
||||
HeaderItem headerItem = ((ListRow) item).getHeaderItem();
|
||||
View view = viewHolder.view;
|
||||
ImageView headerIcon = (ImageView) view.findViewById(R.id.header_icon);
|
||||
TextView headerLabel = (TextView) view.findViewById(R.id.header_label);
|
||||
|
||||
Resources resources = view.getContext().getResources();
|
||||
String streamingPrefix = resources.getString(R.string.streaming_prefix);
|
||||
if (headerItem.getName().startsWith(streamingPrefix)) {
|
||||
Drawable camIcon = resources.getDrawable(R.drawable.ic_videocam_white_24dp);
|
||||
headerIcon.setImageDrawable(camIcon);
|
||||
((ListRow) item).setHeaderItem(new HeaderItem(headerItem.getName()
|
||||
.substring(streamingPrefix.length())));
|
||||
} else {
|
||||
Drawable movieIcon = resources.getDrawable(R.drawable.ic_local_movies_white_24dp);
|
||||
headerIcon.setImageDrawable(movieIcon);
|
||||
headerLabel.setText(headerItem.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package de.nicidienase.chaosflix.leanback;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v17.leanback.widget.ImageCardView;
|
||||
import android.support.v17.leanback.widget.OnItemViewClickedListener;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.support.v17.leanback.widget.RowPresenter;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import de.nicidienase.chaosflix.leanback.activities.DetailsActivity;
|
||||
import de.nicidienase.chaosflix.leanback.activities.EventDetailsActivity;
|
||||
import de.nicidienase.chaosflix.leanback.activities.EventsActivity;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Conference;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Event;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.Room;
|
||||
|
||||
/**
|
||||
* Created by felix on 21.03.17.
|
||||
*/
|
||||
public class ItemViewClickedListener implements OnItemViewClickedListener {
|
||||
|
||||
private static final String TAG = ItemViewClickedListener.class.getSimpleName();
|
||||
private Fragment fragment;
|
||||
|
||||
public ItemViewClickedListener(Fragment fragment) {
|
||||
this.fragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
|
||||
RowPresenter.ViewHolder rowViewHolder, Row row) {
|
||||
Log.d(TAG, "onItemClicked");
|
||||
if (item instanceof Conference) {
|
||||
Conference conference = (Conference) item;
|
||||
// Start EventsActivity for this conference
|
||||
Intent i = new Intent(fragment.getActivity(), EventsActivity.class);
|
||||
i.putExtra(EventsActivity.CONFERENCE, conference);
|
||||
i.putExtra(EventsActivity.CONFERENCE_ID, conference.getApiID());
|
||||
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
fragment.getActivity(),
|
||||
((ImageCardView) itemViewHolder.view).getMainImageView(),
|
||||
EventsActivity.SHARED_ELEMENT_NAME).toBundle();
|
||||
fragment.startActivity(i, bundle);
|
||||
} else if (item instanceof Event) {
|
||||
Event event = (Event) item;
|
||||
Intent i = new Intent(fragment.getActivity(), DetailsActivity.class);
|
||||
i.putExtra(DetailsActivity.TYPE, DetailsActivity.TYPE_RECORDING);
|
||||
i.putExtra(DetailsActivity.EVENT, event);
|
||||
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
fragment.getActivity(),
|
||||
((ImageCardView) itemViewHolder.view).getMainImageView(),
|
||||
EventDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
|
||||
fragment.getActivity().startActivity(i, bundle);
|
||||
} else if (item instanceof Room) {
|
||||
Room room = (Room) item;
|
||||
Intent i = new Intent(fragment.getActivity(), DetailsActivity.class);
|
||||
i.putExtra(DetailsActivity.TYPE, DetailsActivity.TYPE_STREAM);
|
||||
i.putExtra(DetailsActivity.ROOM, room);
|
||||
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
fragment.getActivity(),
|
||||
((ImageCardView) itemViewHolder.view).getMainImageView(),
|
||||
EventDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
|
||||
fragment.getActivity().startActivity(i, bundle);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
package de.nicidienase.chaosflix.leanback;
|
||||
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v17.leanback.app.PlaybackControlGlue;
|
||||
import android.support.v17.leanback.widget.Action;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
|
||||
import android.support.v17.leanback.widget.OnActionClickedListener;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRow;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
|
||||
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
|
||||
import android.util.Log;
|
||||
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Event;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Recording;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.Room;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.StreamUrl;
|
||||
import de.nicidienase.chaosflix.leanback.fragments.OverlayFragment;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
/**
|
||||
* Created by felix on 26.03.17.
|
||||
*/
|
||||
|
||||
public class PlaybackHelper extends PlaybackControlGlue {
|
||||
|
||||
private static final int[] SEEK_SPEEDS = {2, 4, 8, 16};
|
||||
private static final int DEFAULT_UPDATE_PERIOD = 500;
|
||||
private static final int UPDATE_PERIOD = 16;
|
||||
private static final String TAG = PlaybackHelper.class.getSimpleName();
|
||||
private Disposable thumbDisposable;
|
||||
private OverlayFragment.PlaybackControlListener mControlListener;
|
||||
private BitmapDrawable mDrawable = null;
|
||||
private Room room;
|
||||
private StreamUrl stream;
|
||||
private OverlayFragment fragment;
|
||||
private Event event;
|
||||
private Recording recording;
|
||||
private Runnable mUpdateProgressRunnable;
|
||||
private Handler mHandler = new Handler();
|
||||
|
||||
PlaybackControlsRow.PlayPauseAction mPlayPauseAction;
|
||||
PlaybackControlsRow.FastForwardAction mFastForwardAction;
|
||||
PlaybackControlsRow.RewindAction mRewindAction;
|
||||
PlaybackControlsRow.SkipNextAction mSkipNextAction;
|
||||
PlaybackControlsRow.SkipPreviousAction mSkipPreviousAction;
|
||||
private OverlayFragment.PlaybackControlListener mCallback;
|
||||
|
||||
private MediaController mMediaController;
|
||||
private MediaController.TransportControls mTransportControls;
|
||||
private ArrayObjectAdapter adapter;
|
||||
|
||||
public PlaybackHelper(Context context, OverlayFragment fragment, Event event, Recording recording) {
|
||||
super(context, SEEK_SPEEDS);
|
||||
mControlListener = (OverlayFragment.PlaybackControlListener) context;
|
||||
this.fragment = fragment;
|
||||
this.event = event;
|
||||
this.recording = recording;
|
||||
// thumbDisposable = Observable.fromCallable(() ->
|
||||
// new BitmapDrawable(
|
||||
// fragment.getResources(),
|
||||
// Glide.with(getContext())
|
||||
// .load(event.getThumbUrl())
|
||||
// .asBitmap()
|
||||
// .into(-1, -1)
|
||||
// .get()))
|
||||
// .subscribeOn(Schedulers.io())
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .doOnError(Throwable::printStackTrace)
|
||||
// .subscribe(bitmapDrawable -> mDrawable = bitmapDrawable);
|
||||
|
||||
setup();
|
||||
}
|
||||
|
||||
public PlaybackHelper(Context context, OverlayFragment fragment, Room room, StreamUrl stream) {
|
||||
super(context, SEEK_SPEEDS);
|
||||
mControlListener = (OverlayFragment.PlaybackControlListener) context;
|
||||
this.fragment = fragment;
|
||||
this.room = room;
|
||||
this.stream = stream;
|
||||
|
||||
setup();
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
mMediaController = fragment.getActivity().getMediaController();
|
||||
if (mMediaController != null) {
|
||||
mTransportControls = mMediaController.getTransportControls();
|
||||
} else {
|
||||
Log.d(TAG, "Could not get MediaController");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
|
||||
PlaybackControlsRowPresenter presenter = super.createControlsRowAndPresenter();
|
||||
adapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector());
|
||||
getControlsRow().setSecondaryActionsAdapter(adapter);
|
||||
|
||||
mFastForwardAction = (PlaybackControlsRow.FastForwardAction) getPrimaryActionsAdapter()
|
||||
.lookup(ACTION_FAST_FORWARD);
|
||||
|
||||
mRewindAction = (PlaybackControlsRow.RewindAction) getPrimaryActionsAdapter()
|
||||
.lookup(ACTION_REWIND);
|
||||
|
||||
presenter.setOnActionClickedListener(new OnActionClickedListener() {
|
||||
@Override
|
||||
public void onActionClicked(Action action) {
|
||||
dispatchAction(action);
|
||||
}
|
||||
});
|
||||
return presenter;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public void dispatchAction(Action action) {
|
||||
if (action instanceof PlaybackControlsRow.MultiAction) {
|
||||
PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
|
||||
multiAction.nextIndex();
|
||||
notifyActionChanged(multiAction);
|
||||
}
|
||||
if (action == mFastForwardAction) {
|
||||
mTransportControls.fastForward();
|
||||
} else if (action == mRewindAction) {
|
||||
mTransportControls.rewind();
|
||||
} else {
|
||||
super.onActionClicked(action);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyActionChanged(PlaybackControlsRow.MultiAction multiAction) {
|
||||
int index;
|
||||
index = getPrimaryActionsAdapter().indexOf(multiAction);
|
||||
if (index >= 0) {
|
||||
getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
|
||||
} else {
|
||||
index = getSecondaryActionsAdapter().indexOf(multiAction);
|
||||
if (index >= 0) {
|
||||
getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableProgressUpdating(boolean enable) {
|
||||
mHandler.removeCallbacks(mUpdateProgressRunnable);
|
||||
if (enable) {
|
||||
mHandler.post(mUpdateProgressRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress() {
|
||||
if (mUpdateProgressRunnable == null) {
|
||||
mUpdateProgressRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int totalTime = getControlsRow().getTotalTime();
|
||||
long currentPosition = mControlListener.getCurrentPosition();
|
||||
getControlsRow().setCurrentTimeLong(currentPosition);
|
||||
|
||||
long bufferedPosition = mControlListener.getBufferedPosition();
|
||||
getControlsRow().setBufferedProgressLong(bufferedPosition);
|
||||
|
||||
if (totalTime > 0 && totalTime <= currentPosition) {
|
||||
stopProgressAnimation();
|
||||
} else {
|
||||
updateProgress();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
mHandler.postDelayed(mUpdateProgressRunnable, getUpdatePeriod());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasValidMedia() {
|
||||
return mediaIsRecording() || mediaIsStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMediaPlaying() {
|
||||
return mControlListener.isMediaPlaying();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getMediaTitle() {
|
||||
if (mediaIsRecording()) {
|
||||
return event.getTitle();
|
||||
}
|
||||
if (mediaIsStream()) {
|
||||
return room.getDisplay();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getMediaSubtitle() {
|
||||
if (mediaIsRecording()) {
|
||||
return event.getSubtitle();
|
||||
}
|
||||
if (mediaIsStream()) {
|
||||
return stream.getDisplay();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMediaDuration() {
|
||||
if (mediaIsRecording()) {
|
||||
return event.getLength() * 1000;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getMediaArt() {
|
||||
return mDrawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startPlayback(int speed) {
|
||||
if (getCurrentSpeedId() == speed) {
|
||||
return;
|
||||
}
|
||||
if (mTransportControls != null) {
|
||||
mTransportControls.play();
|
||||
} else {
|
||||
mControlListener.play();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void pausePlayback() {
|
||||
if (mTransportControls != null) {
|
||||
mTransportControls.pause();
|
||||
} else {
|
||||
mControlListener.pause();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void skipToNext() {
|
||||
mTransportControls.skipToNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void skipToPrevious() {
|
||||
mTransportControls.skipToPrevious();
|
||||
}
|
||||
|
||||
private void stopProgressAnimation() {
|
||||
if (mHandler != null && mUpdateProgressRunnable != null) {
|
||||
mHandler.removeCallbacks(mUpdateProgressRunnable);
|
||||
mUpdateProgressRunnable = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSupportedActions() {
|
||||
return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND | ACTION_SKIP_TO_PREVIOUS |
|
||||
ACTION_SKIP_TO_NEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentSpeedId() {
|
||||
return isMediaPlaying() ? PLAYBACK_SPEED_NORMAL : PLAYBACK_SPEED_PAUSED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentPosition() {
|
||||
if (mControlListener != null) {
|
||||
return (int) mControlListener.getCurrentPosition();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getCurrentPositionLong() {
|
||||
if (mControlListener != null) {
|
||||
return mControlListener.getCurrentPosition();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private boolean mediaIsStream() {
|
||||
return (room != null && stream != null);
|
||||
}
|
||||
|
||||
private boolean mediaIsRecording() {
|
||||
return (event != null && recording != null);
|
||||
}
|
||||
|
||||
public MediaController.Callback createMediaControllerCallback() {
|
||||
return new ChaosflixMediaControllerCallback();
|
||||
}
|
||||
|
||||
private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
|
||||
return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
|
||||
}
|
||||
|
||||
private ArrayObjectAdapter getSecondaryActionsAdapter() {
|
||||
return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
|
||||
}
|
||||
|
||||
private class ChaosflixMediaControllerCallback extends MediaController.Callback {
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(@NonNull PlaybackState state) {
|
||||
if (state.getState() != PlaybackState.STATE_NONE) {
|
||||
updateProgress();
|
||||
}
|
||||
onStateChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataChanged(@Nullable MediaMetadata metadata) {
|
||||
PlaybackHelper.this.onMetadataChanged();
|
||||
PlaybackHelper.this.adapter.notifyArrayItemRangeChanged(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void onStop() {
|
||||
if (thumbDisposable != null) {
|
||||
thumbDisposable.dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package de.nicidienase.chaosflix.leanback.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import de.nicidienase.chaosflix.R;
|
||||
|
||||
/**
|
||||
* Created by felix on 18.03.17.
|
||||
*/
|
||||
|
||||
public class ConferencesActivity extends LeanbackBaseActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// setContentView(R.layout.activity_conferences_grid);
|
||||
setContentView(R.layout.activity_conferences_browse);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.nicidienase.chaosflix.leanback.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import de.nicidienase.chaosflix.R;
|
||||
|
||||
/*
|
||||
* Details activity class that loads LeanbackDetailsFragment class
|
||||
*/
|
||||
public class DetailsActivity extends LeanbackBaseActivity {
|
||||
|
||||
public static final String SHARED_ELEMENT_NAME = "hero";
|
||||
public static final String EVENT = "event";
|
||||
public static final String ROOM = "room";
|
||||
public static final String STREAM_URL = "stream_url";
|
||||
public static final String RECORDING = "recording";
|
||||
public static final String TYPE = "event_type";
|
||||
public static final int TYPE_RECORDING = 0;
|
||||
public static final int TYPE_STREAM = 1;
|
||||
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_event_details);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package de.nicidienase.chaosflix.leanback.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.PersistableBundle;
|
||||
|
||||
import de.nicidienase.chaosflix.R;
|
||||
|
||||
/**
|
||||
* Created by felix on 18.03.17.
|
||||
*/
|
||||
|
||||
public class EventDetailsActivity extends LeanbackBaseActivity {
|
||||
|
||||
public static final String EVENT = "event";
|
||||
public static final String SHARED_ELEMENT_NAME = "transision_element";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
|
||||
super.onCreate(savedInstanceState, persistentState);
|
||||
setContentView(R.layout.activity_event_details);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package de.nicidienase.chaosflix.leanback.activities;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import de.nicidienase.chaosflix.R;
|
||||
|
||||
public class EventsActivity extends LeanbackBaseActivity {
|
||||
|
||||
public static final String CONFERENCE_ID = "conference_id";
|
||||
public static final String CONFERENCE = "conference";
|
||||
public static final String SHARED_ELEMENT_NAME = "shared_element";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// TODO determin if we should use a Browse or a Grid layout
|
||||
setContentView(R.layout.activity_events_browse);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package de.nicidienase.chaosflix.leanback.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.PersistableBundle;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import de.nicidienase.chaosflix.common.network.MediaApiService;
|
||||
import io.reactivex.Single;
|
||||
|
||||
/**
|
||||
* Created by felix on 24.03.17.
|
||||
*/
|
||||
|
||||
public class LeanbackBaseActivity extends Activity {
|
||||
private MediaApiService mMediaApiService = null;
|
||||
private ServiceConnection conn;
|
||||
private boolean mConnected = false;
|
||||
private String serverUrl = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
|
||||
super.onCreate(savedInstanceState, persistentState);
|
||||
serverUrl = getIntent().getStringExtra("server_url");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (mConnected) {
|
||||
unbindService(conn);
|
||||
conn = null;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
||||
public Single<MediaApiService> getApiServiceObservable() {
|
||||
Intent s = new Intent(this, MediaApiService.class);
|
||||
if (serverUrl != null) {
|
||||
s.putExtra(MediaApiService.RECORDING_URL, serverUrl);
|
||||
s.putExtra(MediaApiService.STREAMING_URL, serverUrl);
|
||||
}
|
||||
return Single.create(e -> {
|
||||
if (mMediaApiService != null) {
|
||||
e.onSuccess(mMediaApiService);
|
||||
} else {
|
||||
conn = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mConnected = true;
|
||||
mMediaApiService = ((MediaApiService.LocalBinder) service).getService();
|
||||
e.onSuccess(mMediaApiService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mMediaApiService = null;
|
||||
mConnected = false;
|
||||
}
|
||||
};
|
||||
LeanbackBaseActivity.this.bindService(s, conn, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,330 @@
|
|||
package de.nicidienase.chaosflix.leanback.activities;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||
import com.google.android.exoplayer2.LoadControl;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
||||
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
|
||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
|
||||
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
||||
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import de.nicidienase.chaosflix.R;
|
||||
import de.nicidienase.chaosflix.leanback.fragments.OverlayFragment;
|
||||
|
||||
/**
|
||||
* Created by felix on 26.03.17.
|
||||
*/
|
||||
|
||||
public class PlayerActivity extends LeanbackBaseActivity
|
||||
implements OverlayFragment.PlaybackControlListener {
|
||||
|
||||
private static final String TAG = PlayerActivity.class.getSimpleName();
|
||||
@BindView(R.id.videoView)
|
||||
SurfaceView mSurfaceView;
|
||||
@BindView(R.id.loading_player)
|
||||
ProgressBar loadingSpinner;
|
||||
OverlayFragment mPlaybackControllFragment;
|
||||
private DefaultBandwidthMeter bandwidthMeter;
|
||||
private SimpleExoPlayer player;
|
||||
private String mUserAgent;
|
||||
|
||||
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
|
||||
private Handler mainHandler;
|
||||
private DefaultTrackSelector trackSelector;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_playback);
|
||||
ButterKnife.bind(this);
|
||||
mSurfaceView.setKeepScreenOn(true);
|
||||
|
||||
mPlaybackControllFragment = (OverlayFragment) getFragmentManager().findFragmentById(R.id.playback_controls_fragment);
|
||||
|
||||
mUserAgent = Util.getUserAgent(this, getResources().getString(R.string.app_name));
|
||||
synchronized (this) {
|
||||
if (player == null) {
|
||||
setupPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
showLoadingSpinner();
|
||||
// TODO get persisted playback progress
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
pause();
|
||||
// TODO persist playback progress
|
||||
}
|
||||
|
||||
private void setupPlayer() {
|
||||
mUserAgent = Util.getUserAgent(this, getResources().getString(R.string.app_name));
|
||||
|
||||
mainHandler = new Handler();
|
||||
bandwidthMeter = new DefaultBandwidthMeter();
|
||||
TrackSelection.Factory videoTrackSelectionFactory
|
||||
= new AdaptiveTrackSelection.Factory(bandwidthMeter);
|
||||
trackSelector
|
||||
= new DefaultTrackSelector(videoTrackSelectionFactory);
|
||||
LoadControl loadControl = new DefaultLoadControl();
|
||||
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, loadControl);
|
||||
player.setVideoSurfaceView(mSurfaceView);
|
||||
|
||||
player.setVideoListener(new SimpleExoPlayer.VideoListener() {
|
||||
@Override
|
||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRenderedFirstFrame() {
|
||||
hideLoadingSpinner();
|
||||
}
|
||||
});
|
||||
player.addListener(new ExoPlayer.EventListener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, Object manifest) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingChanged(boolean isLoading) {
|
||||
if(isLoading && player.getPlaybackState() != ExoPlayer.STATE_READY){
|
||||
showLoadingSpinner();
|
||||
} else{
|
||||
hideLoadingSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
switch (playbackState){
|
||||
case ExoPlayer.STATE_BUFFERING:
|
||||
if(player.isLoading()){
|
||||
showLoadingSpinner();
|
||||
}
|
||||
break;
|
||||
case ExoPlayer.STATE_ENDED:
|
||||
Log.d(TAG,"Finished Playback");
|
||||
finish();
|
||||
break;
|
||||
case ExoPlayer.STATE_IDLE:
|
||||
case ExoPlayer.STATE_READY:
|
||||
default:
|
||||
hideLoadingSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRepeatModeChanged(int repeatMode) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException error) {
|
||||
String errorMessage = error.getCause().getMessage();
|
||||
Toast.makeText(PlayerActivity.this,errorMessage,Toast.LENGTH_SHORT).show();
|
||||
Log.d(TAG,errorMessage,error);
|
||||
PlayerActivity.this.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPositionDiscontinuity() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showLoadingSpinner() {
|
||||
loadingSpinner.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void hideLoadingSpinner(){
|
||||
loadingSpinner.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVideoSource(String source) {
|
||||
Log.d(TAG, "Source: " + source);
|
||||
synchronized (this) {
|
||||
if (player == null) {
|
||||
setupPlayer();
|
||||
}
|
||||
}
|
||||
MediaSource mediaSource = buildMediaSource(Uri.parse(source), "");
|
||||
player.prepare(mediaSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play() {
|
||||
player.setPlayWhenReady(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
player.setPlayWhenReady(false);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playPause() {
|
||||
player.setPlayWhenReady(!player.getPlayWhenReady());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(long sec) {
|
||||
player.seekTo(sec * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMediaPlaying() {
|
||||
if (player != null) {
|
||||
return player.getPlayWhenReady();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCurrentPosition() {
|
||||
if (player != null) {
|
||||
return player.getCurrentPosition();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releasePlayer() {
|
||||
if (player != null) {
|
||||
player.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBufferedPosition() {
|
||||
if (player != null) {
|
||||
return player.getBufferedPosition();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mute(boolean state) {
|
||||
if (player != null) {
|
||||
player.setVolume(state ? 0.0f : 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength() {
|
||||
if (player != null) {
|
||||
return player.getDuration();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nextAudioStream() {
|
||||
// TODO cycle through audio streams
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skipForward(int sec) {
|
||||
player.seekTo(player.getCurrentPosition() + (sec * 1000));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skipBackward(int sec) {
|
||||
player.seekTo(player.getCurrentPosition() - (sec * 1000));
|
||||
}
|
||||
|
||||
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
|
||||
DataSource.Factory mediaDataSourceFactory = buildDataSourceFactory(true);
|
||||
int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri)
|
||||
: Util.inferContentType("." + overrideExtension);
|
||||
switch (type) {
|
||||
case C.TYPE_SS:
|
||||
return new SsMediaSource(uri, buildDataSourceFactory(false),
|
||||
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, null);
|
||||
case C.TYPE_DASH:
|
||||
return new DashMediaSource(uri, buildDataSourceFactory(false),
|
||||
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, null);
|
||||
case C.TYPE_HLS:
|
||||
return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, null);
|
||||
case C.TYPE_OTHER:
|
||||
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
|
||||
mainHandler, null);
|
||||
default: {
|
||||
throw new IllegalStateException("Unsupported type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {
|
||||
return buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
|
||||
}
|
||||
|
||||
private DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
|
||||
return new DefaultDataSourceFactory(this, bandwidthMeter,
|
||||
buildHttpDataSourceFactory(bandwidthMeter));
|
||||
}
|
||||
|
||||
private HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
|
||||
return new DefaultHttpDataSourceFactory(mUserAgent, bandwidthMeter,
|
||||
DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
|
||||
DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS,
|
||||
true /* allowCrossProtocolRedirects */);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package de.nicidienase.chaosflix.leanback.fragments;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v17.leanback.app.ErrorFragment;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import de.nicidienase.chaosflix.R;
|
||||
|
||||
/**
|
||||
* Created by felix on 22.03.17.
|
||||
*/
|
||||
|
||||
public class BrowseErrorFragment extends ErrorFragment {
|
||||
|
||||
private static final boolean TRANSLUCENT = true;
|
||||
public static final String FRAGMENT = "fragmentId";
|
||||
private SpinnerFragment mSpinnerFragment;
|
||||
|
||||
public static BrowseErrorFragment showErrorFragment(FragmentManager manager, int fragmentId) {
|
||||
BrowseErrorFragment errorFragment = new BrowseErrorFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(BrowseErrorFragment.FRAGMENT, fragmentId);
|
||||
errorFragment.setArguments(args);
|
||||
manager.beginTransaction().replace(fragmentId, errorFragment)
|
||||
.addToBackStack(null).commit();
|
||||
return errorFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
int fragmentId = getArguments().getInt(FRAGMENT);
|
||||
mSpinnerFragment = new SpinnerFragment();
|
||||
getFragmentManager().beginTransaction().add(fragmentId, mSpinnerFragment).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
getFragmentManager().beginTransaction().remove(mSpinnerFragment).commit();
|
||||
}
|
||||
|
||||
public void setErrorContent(int resourceId) {
|
||||
setErrorContent(getResources().getString(resourceId));
|
||||
}
|
||||
|
||||
public void setErrorContent(String message) {
|
||||
setImageDrawable(getResources().getDrawable(R.drawable.lb_ic_sad_cloud, null));
|
||||
setMessage(message);
|
||||
setDefaultBackground(TRANSLUCENT);
|
||||
|
||||
setButtonText(getResources().getString(R.string.dismiss_error));
|
||||
setButtonClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View arg0) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
FragmentManager fragmentManager = getFragmentManager();
|
||||
if (fragmentManager != null) {
|
||||
fragmentManager.beginTransaction().remove(BrowseErrorFragment.this).commit();
|
||||
fragmentManager.popBackStack();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class SpinnerFragment extends Fragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
ProgressBar progressBar = new ProgressBar(container.getContext());
|
||||
if (container instanceof FrameLayout) {
|
||||
Resources res = getResources();
|
||||
int width = res.getDimensionPixelSize(R.dimen.spinner_width);
|
||||
int height = res.getDimensionPixelSize(R.dimen.spinner_height);
|
||||
FrameLayout.LayoutParams layoutParams =
|
||||
new FrameLayout.LayoutParams(width, height, Gravity.CENTER);
|
||||
progressBar.setLayoutParams(layoutParams);
|
||||
}
|
||||
return progressBar;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
package de.nicidienase.chaosflix.leanback.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v17.leanback.app.BrowseFragment;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.SectionRow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import de.nicidienase.chaosflix.BuildConfig;
|
||||
import de.nicidienase.chaosflix.leanback.CardPresenter;
|
||||
import de.nicidienase.chaosflix.leanback.ItemViewClickedListener;
|
||||
import de.nicidienase.chaosflix.R;
|
||||
import de.nicidienase.chaosflix.leanback.activities.LeanbackBaseActivity;
|
||||
import de.nicidienase.chaosflix.common.entities.WatchlistItem;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Conference;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.ConferencesWrapper;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.Group;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.LiveConference;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
/**
|
||||
* Created by felix on 21.03.17.
|
||||
*/
|
||||
|
||||
public class ConferencesBrowseFragment extends BrowseFragment {
|
||||
|
||||
private static final String TAG = ConferencesBrowseFragment.class.getSimpleName();
|
||||
public static final int FRAGMENT = R.id.browse_fragment;
|
||||
private ArrayObjectAdapter mRowsAdapter;
|
||||
private Map<String, List<Conference>> mConferences;
|
||||
CompositeDisposable mDisposables = new CompositeDisposable();
|
||||
private ArrayObjectAdapter watchListAdapter;
|
||||
private ListRow mWatchlistRow;
|
||||
private boolean streamsAvailable = false;
|
||||
private SectionRow mStreamingSection;
|
||||
private SectionRow mRecomendationsSectionsRow;
|
||||
private SectionRow mConferencesSection;
|
||||
private boolean mWatchlistVisible = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(getResources().getString(R.string.app_name));
|
||||
|
||||
// setHeaderPresenterSelector(new PresenterSelector() {
|
||||
// @Override
|
||||
// public Presenter getPresenter(Object item) {
|
||||
// return new HeaderItemPresenter();
|
||||
// }
|
||||
// });
|
||||
|
||||
final BrowseErrorFragment errorFragment =
|
||||
BrowseErrorFragment.showErrorFragment(getFragmentManager(), FRAGMENT);
|
||||
CardPresenter cardPresenter = new CardPresenter();
|
||||
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
|
||||
watchListAdapter = new ArrayObjectAdapter(cardPresenter);
|
||||
|
||||
Disposable disposable = ((LeanbackBaseActivity) getActivity()).getApiServiceObservable()
|
||||
.subscribe(mediaApiService -> {
|
||||
mDisposables.add(Observable.zip(mediaApiService.getStreamingConferences(),
|
||||
mediaApiService.getConferences(),
|
||||
(liveConferences, conferencesWrapper) -> {
|
||||
ArrayList<Object> list = new ArrayList<>();
|
||||
list.add(liveConferences);
|
||||
list.add(conferencesWrapper);
|
||||
return list;
|
||||
})
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(objects -> {
|
||||
List<LiveConference> liveConferences = (List<LiveConference>) objects.get(0);
|
||||
ConferencesWrapper conferences = (ConferencesWrapper) objects.get(1);
|
||||
if (BuildConfig.DEBUG) {
|
||||
liveConferences.add(LiveConference.getDummyObject());
|
||||
}
|
||||
streamsAvailable = liveConferences.size() > 0;
|
||||
addStreams(cardPresenter, liveConferences);
|
||||
addRecordings(cardPresenter, conferences);
|
||||
errorFragment.dismiss();
|
||||
setOnItemViewClickedListener(new ItemViewClickedListener(this));
|
||||
setAdapter(mRowsAdapter);
|
||||
setSelectedPosition(1);
|
||||
}));
|
||||
});
|
||||
mDisposables.add(disposable);
|
||||
}
|
||||
|
||||
private void addRecordings(CardPresenter cardPresenter, ConferencesWrapper conferences) {
|
||||
mConferencesSection = new SectionRow(getString(R.string.conferences));
|
||||
mRowsAdapter.add(mConferencesSection);
|
||||
mConferences = conferences.getConferencesBySeries();
|
||||
Set<String> keySet = mConferences.keySet();
|
||||
for (String tag : ConferencesWrapper.getOrderedConferencesList()) {
|
||||
if (keySet.contains(tag)) {
|
||||
ListRow row = getRow(mConferences, cardPresenter, tag, "");
|
||||
mRowsAdapter.add(row);
|
||||
}
|
||||
}
|
||||
for (String tag : keySet) {
|
||||
if (!ConferencesWrapper.getOrderedConferencesList().contains(tag)) {
|
||||
mRowsAdapter.add(getRow(mConferences, cardPresenter, tag, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addStreams(CardPresenter cardPresenter, List<LiveConference> liveConferences) {
|
||||
if (liveConferences.size() > 0) {
|
||||
HeaderItem streamingHeader = new HeaderItem(getString(R.string.livestreams));
|
||||
mStreamingSection = new SectionRow(streamingHeader);
|
||||
mRowsAdapter.add(0, mStreamingSection);
|
||||
for (LiveConference con : liveConferences) {
|
||||
if(!con.getConference().equals("Sendeschleife") || BuildConfig.DEBUG){
|
||||
int i = -1;
|
||||
for (i = 0; i < con.getGroups().size(); i++) {
|
||||
Group g = con.getGroups().get(i);
|
||||
// setup header
|
||||
String group = g.getGroup().length() > 0 ? g.getGroup() : con.getConference();
|
||||
HeaderItem header = new HeaderItem(group);
|
||||
header.setDescription(con.getConference() + " - " + con.getDescription());
|
||||
header.setContentDescription(group);
|
||||
// setup list
|
||||
ArrayObjectAdapter listRowAdapter
|
||||
= new ArrayObjectAdapter(cardPresenter);
|
||||
listRowAdapter.addAll(listRowAdapter.size(), g.getRooms());
|
||||
mRowsAdapter.add(i + 1, new ListRow(header, listRowAdapter));
|
||||
}
|
||||
}
|
||||
// mRowsAdapter.add(i + 1, new DividerRow());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
List<WatchlistItem> watchlistItems = new ArrayList<>();
|
||||
Iterator<WatchlistItem> all = WatchlistItem.findAll(WatchlistItem.class);
|
||||
while (all.hasNext()){
|
||||
watchlistItems.add(all.next());
|
||||
}
|
||||
// = Lists.newArrayList(WatchlistItem.findAll(WatchlistItem.class));
|
||||
// setup and list items
|
||||
updateWatchlist(watchlistItems);
|
||||
}
|
||||
|
||||
private void showWatchlist() {
|
||||
if(mWatchlistRow == null && mRecomendationsSectionsRow == null){
|
||||
mRecomendationsSectionsRow = new SectionRow(getString(R.string.recomendations));
|
||||
HeaderItem header = new HeaderItem(getString(R.string.watchlist));
|
||||
mWatchlistRow = new ListRow(header, watchListAdapter);
|
||||
}
|
||||
int offset = getWatchlistOffset();
|
||||
if(!mWatchlistVisible){
|
||||
mRowsAdapter.add(offset, mRecomendationsSectionsRow);
|
||||
mRowsAdapter.add(offset + 1, mWatchlistRow);
|
||||
mWatchlistVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Disposable updateWatchlist(List<WatchlistItem> watchlistItems) {
|
||||
return ((LeanbackBaseActivity) getActivity()).getApiServiceObservable()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(mediaApiService -> {
|
||||
showWatchlist();
|
||||
watchListAdapter.clear();
|
||||
if(watchlistItems.size() > 0){
|
||||
// int i = Math.max(0,mRowsAdapter.indexOf(mConferencesSection));
|
||||
// mRowsAdapter.add(i,mRecomendationsSectionsRow);
|
||||
// mRowsAdapter.add(i+1,watchListAdapter);
|
||||
Observable.fromIterable(watchlistItems)
|
||||
.flatMap(watchlistItem -> mediaApiService.getEvent(watchlistItem.getEventId()))
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(event -> watchListAdapter.add(event));
|
||||
} else {
|
||||
// watchListAdapter.add("Watchlist empty");
|
||||
hideWatchlist();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void hideWatchlist() {
|
||||
if(mWatchlistVisible){
|
||||
int i = mRowsAdapter.indexOf(mRecomendationsSectionsRow);
|
||||
mRowsAdapter.removeItems(i,2);
|
||||
mWatchlistVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
private int getWatchlistOffset() {
|
||||
if(mRowsAdapter.size()>0 || streamsAvailable){
|
||||
int i = mRowsAdapter.indexOf(mConferencesSection);
|
||||
return Math.max(i,0);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
mDisposables.dispose();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
private ListRow getRow(Map<String, List<Conference>> conferences, CardPresenter cardPresenter, String tag, String description) {
|
||||
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
|
||||
listRowAdapter.addAll(0, conferences.get(tag));
|
||||
HeaderItem header = new HeaderItem(ConferencesWrapper.getStringForTag(tag));
|
||||
header.setDescription(description);
|
||||
return new ListRow(header, listRowAdapter);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package de.nicidienase.chaosflix.leanback.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v17.leanback.app.VerticalGridFragment;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.VerticalGridPresenter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import de.nicidienase.chaosflix.leanback.CardPresenter;
|
||||
import de.nicidienase.chaosflix.leanback.ItemViewClickedListener;
|
||||
import de.nicidienase.chaosflix.leanback.activities.LeanbackBaseActivity;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Conference;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
||||
/**
|
||||
* Created by felix on 20.03.17.
|
||||
*/
|
||||
|
||||
public class ConferencesGridFragment extends VerticalGridFragment {
|
||||
|
||||
private static final int NUM_COLUMNS = 5;
|
||||
private static final String TAG = ConferencesGridFragment.class.getSimpleName();
|
||||
private final ArrayObjectAdapter mConferenceAdapter =
|
||||
new ArrayObjectAdapter(new CardPresenter());
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
((LeanbackBaseActivity) getActivity()).getApiServiceObservable()
|
||||
.subscribe(mediaApiService -> {
|
||||
mediaApiService.getConferences()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(conferences -> {
|
||||
List<Conference> conferenceList = conferences.getConferences();
|
||||
Collections.sort(conferenceList);
|
||||
Collections.reverse(conferenceList);
|
||||
mConferenceAdapter.addAll(0, conferenceList);
|
||||
setAdapter(mConferenceAdapter);
|
||||
});
|
||||
});
|
||||
setTitle("Chaosflix");
|
||||
|
||||
VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
|
||||
gridPresenter.setNumberOfColumns(NUM_COLUMNS);
|
||||
setGridPresenter(gridPresenter);
|
||||
|
||||
// After 500ms, start the animation to transition the cards into view.
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
startEntranceTransition();
|
||||
}
|
||||
}, 500);
|
||||
|
||||
setOnItemViewClickedListener(new ItemViewClickedListener(this));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package de.nicidienase.chaosflix.leanback.fragments;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v17.leanback.app.BackgroundManager;
|
||||
import android.support.v17.leanback.app.BrowseFragment;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.support.v17.leanback.widget.RowPresenter;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import de.nicidienase.chaosflix.leanback.CardPresenter;
|
||||
import de.nicidienase.chaosflix.leanback.ItemViewClickedListener;
|
||||
import de.nicidienase.chaosflix.R;
|
||||
import de.nicidienase.chaosflix.leanback.activities.LeanbackBaseActivity;
|
||||
import de.nicidienase.chaosflix.leanback.activities.EventsActivity;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Conference;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Event;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
||||
public class EventsBrowseFragment extends BrowseFragment {
|
||||
private static final String TAG = EventsBrowseFragment.class.getSimpleName();
|
||||
|
||||
private static final int BACKGROUND_UPDATE_DELAY = 300;
|
||||
private static final int FRAGMENT = R.id.browse_fragment;
|
||||
|
||||
private final Handler mHandler = new Handler();
|
||||
private ArrayObjectAdapter mRowsAdapter;
|
||||
private Drawable mDefaultBackground;
|
||||
private DisplayMetrics mMetrics;
|
||||
private Timer mBackgroundTimer;
|
||||
private URI mBackgroundURI;
|
||||
private BackgroundManager mBackgroundManager;
|
||||
private Conference mConference;
|
||||
private int conferenceId;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
Log.i(TAG, "onCreate");
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
final BrowseErrorFragment errorFragment =
|
||||
BrowseErrorFragment.showErrorFragment(getFragmentManager(), FRAGMENT);
|
||||
conferenceId = this.getActivity().getIntent().getIntExtra(EventsActivity.CONFERENCE_ID, 0);
|
||||
mConference = this.getActivity().getIntent().getParcelableExtra(EventsActivity.CONFERENCE);
|
||||
|
||||
((LeanbackBaseActivity) getActivity()).getApiServiceObservable()
|
||||
.subscribe(mediaApiService -> {
|
||||
mediaApiService.getConference(mConference.getApiID())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnError(t -> errorFragment.setErrorContent(t.getMessage()))
|
||||
.subscribe(conference -> {
|
||||
mConference = conference;
|
||||
setupUIElements();
|
||||
loadRows();
|
||||
errorFragment.dismiss();
|
||||
});
|
||||
});
|
||||
|
||||
prepareBackgroundManager();
|
||||
setOnItemViewClickedListener(new ItemViewClickedListener(this));
|
||||
setOnItemViewSelectedListener(new ItemViewSelectedListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (null != mBackgroundTimer) {
|
||||
Log.d(TAG, "onDestroy: " + mBackgroundTimer.toString());
|
||||
mBackgroundTimer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadRows() {
|
||||
HashMap<String, List<Event>> eventsByTags = mConference.getEventsByTags();
|
||||
|
||||
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
|
||||
CardPresenter cardPresenter = new CardPresenter();
|
||||
|
||||
List<Event> other = new LinkedList<Event>();
|
||||
// List<String> keys = Lists.newArrayList(eventsByTags.keySet());
|
||||
List<String> keys = new ArrayList<>();
|
||||
Iterator<String> iterator = eventsByTags.keySet().iterator();
|
||||
while (iterator.hasNext()){
|
||||
keys.add(iterator.next());
|
||||
}
|
||||
Collections.sort(keys);
|
||||
for (String tag : keys) {
|
||||
List<Event> items = eventsByTags.get(tag);
|
||||
Collections.sort(items);
|
||||
if (android.text.TextUtils.isDigitsOnly(tag)) {
|
||||
other.addAll(items);
|
||||
} else {
|
||||
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
|
||||
listRowAdapter.addAll(0, items);
|
||||
HeaderItem header = new HeaderItem(tag);
|
||||
mRowsAdapter.add(new ListRow(header, listRowAdapter));
|
||||
}
|
||||
}
|
||||
if (other.size() > 0) {
|
||||
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
|
||||
listRowAdapter.addAll(0, other);
|
||||
HeaderItem header = new HeaderItem("other");
|
||||
mRowsAdapter.add(new ListRow(header, listRowAdapter));
|
||||
}
|
||||
setAdapter(mRowsAdapter);
|
||||
}
|
||||
|
||||
private void prepareBackgroundManager() {
|
||||
mBackgroundManager = BackgroundManager.getInstance(getActivity());
|
||||
mBackgroundManager.attach(getActivity().getWindow());
|
||||
mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
|
||||
mMetrics = new DisplayMetrics();
|
||||
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
|
||||
}
|
||||
|
||||
private void setupUIElements() {
|
||||
Glide.with(getActivity())
|
||||
.load(mConference.getLogoUrl())
|
||||
.centerCrop()
|
||||
.error(mDefaultBackground)
|
||||
.into(new SimpleTarget<GlideDrawable>(432, 243) {
|
||||
@Override
|
||||
public void onResourceReady(GlideDrawable resource,
|
||||
GlideAnimation<? super GlideDrawable>
|
||||
glideAnimation) {
|
||||
setBadgeDrawable(resource);
|
||||
}
|
||||
});
|
||||
setTitle(mConference.getTitle()); // Badge, when set, takes precedent
|
||||
// over title
|
||||
setHeadersState(HEADERS_ENABLED);
|
||||
setHeadersTransitionOnBackEnabled(true);
|
||||
|
||||
// set fastLane (or headers) background color
|
||||
setBrandColor(getResources().getColor(R.color.fastlane_background));
|
||||
// set search icon color
|
||||
setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
|
||||
|
||||
}
|
||||
|
||||
protected void updateBackground(String uri) {
|
||||
int width = mMetrics.widthPixels;
|
||||
int height = mMetrics.heightPixels;
|
||||
Glide.with(getActivity())
|
||||
.load(uri)
|
||||
.centerCrop()
|
||||
.error(mDefaultBackground)
|
||||
.into(new SimpleTarget<GlideDrawable>(width, height) {
|
||||
@Override
|
||||
public void onResourceReady(GlideDrawable resource,
|
||||
GlideAnimation<? super GlideDrawable>
|
||||
glideAnimation) {
|
||||
mBackgroundManager.setDrawable(resource);
|
||||
}
|
||||
});
|
||||
mBackgroundTimer.cancel();
|
||||
}
|
||||
|
||||
private void startBackgroundTimer() {
|
||||
if (null != mBackgroundTimer) {
|
||||
mBackgroundTimer.cancel();
|
||||
}
|
||||
mBackgroundTimer = new Timer();
|
||||
mBackgroundTimer.schedule(new UpdateBackgroundTask(), BACKGROUND_UPDATE_DELAY);
|
||||
}
|
||||
|
||||
private final class ItemViewSelectedListener implements OnItemViewSelectedListener {
|
||||
@Override
|
||||
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
|
||||
RowPresenter.ViewHolder rowViewHolder, Row row) {
|
||||
if (item instanceof Event) {
|
||||
try {
|
||||
mBackgroundURI = new URI(((Event) item).getPosterUrl());
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// TODO make configurable (enable/disable)
|
||||
// startBackgroundTimer();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateBackgroundTask extends TimerTask {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mBackgroundURI != null) {
|
||||
updateBackground(mBackgroundURI.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,443 @@
|
|||
package de.nicidienase.chaosflix.leanback.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v17.leanback.app.BackgroundManager;
|
||||
import android.support.v17.leanback.app.DetailsFragment;
|
||||
import android.support.v17.leanback.widget.Action;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.ClassPresenterSelector;
|
||||
import android.support.v17.leanback.widget.DetailsOverviewLogoPresenter;
|
||||
import android.support.v17.leanback.widget.DetailsOverviewRow;
|
||||
import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
|
||||
import android.support.v17.leanback.widget.FullWidthDetailsOverviewSharedElementHelper;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.OnActionClickedListener;
|
||||
import android.support.v17.leanback.widget.Presenter;
|
||||
import android.app.AlertDialog;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import de.nicidienase.chaosflix.leanback.CardPresenter;
|
||||
import de.nicidienase.chaosflix.leanback.EventDetailsDescriptionPresenter;
|
||||
import de.nicidienase.chaosflix.leanback.ItemViewClickedListener;
|
||||
import de.nicidienase.chaosflix.R;
|
||||
import de.nicidienase.chaosflix.leanback.activities.ConferencesActivity;
|
||||
import de.nicidienase.chaosflix.leanback.activities.DetailsActivity;
|
||||
import de.nicidienase.chaosflix.leanback.activities.EventDetailsActivity;
|
||||
import de.nicidienase.chaosflix.leanback.activities.LeanbackBaseActivity;
|
||||
import de.nicidienase.chaosflix.leanback.activities.PlayerActivity;
|
||||
import de.nicidienase.chaosflix.common.entities.WatchlistItem;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Conference;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Event;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Recording;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.Group;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.LiveConference;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.Room;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.Stream;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.StreamUrl;
|
||||
import de.nicidienase.chaosflix.common.network.MediaApiService;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
||||
/**
|
||||
* Created by felix on 18.03.17.
|
||||
*/
|
||||
|
||||
public class EventsDetailsFragment extends DetailsFragment {
|
||||
private static final int DETAIL_THUMB_WIDTH = 254;
|
||||
private static final int DETAIL_THUMB_HEIGHT = 143;
|
||||
private static final int NUM_RELATED_TALKS = 5;
|
||||
private static final int NUM_RANDOM_TALKS = NUM_RELATED_TALKS;
|
||||
|
||||
private static final String TAG = EventsDetailsFragment.class.getSimpleName();
|
||||
public static final int FRAGMENT = R.id.details_fragment;
|
||||
public static final int DUMMY_ID = 1646465164;
|
||||
public static final int DEFAULT_DRAWABLE = R.drawable.default_background;
|
||||
private static final long ADD_WATCHLIST_ACTION = 1646465165;
|
||||
private static final long REMOVE_WATCHLIST_ACTION = 1646465166;
|
||||
private Event mSelectedEvent;
|
||||
private MediaApiService mMediaApiService;
|
||||
private Room mRoom;
|
||||
private int eventType;
|
||||
private ArrayList<StreamUrl> streamUrlList;
|
||||
private BackgroundManager mBackgroundmanager;
|
||||
private DisplayMetrics mMetrics;
|
||||
private WatchlistItem mWatchlistItem;
|
||||
private ArrayObjectAdapter mRecordingActionsAdapter;
|
||||
private OnActionClickedListener mOnActionClickedListener = new OnActionClickedListener() {
|
||||
@Override
|
||||
public void onActionClicked(Action action) {
|
||||
Log.d(TAG, "OnActionClicked");
|
||||
if (action.getId() == ADD_WATCHLIST_ACTION) {
|
||||
new WatchlistItem(mSelectedEvent.getApiID()).save();
|
||||
mRecordingActionsAdapter.replace(0, new Action(REMOVE_WATCHLIST_ACTION, EventsDetailsFragment.this.getString(R.string.remove_from_watchlist)));
|
||||
SharedPreferences preferences = getActivity().getSharedPreferences(getString(R.string.watchlist_preferences_key), Context.MODE_PRIVATE);
|
||||
if(preferences.getBoolean(getString(R.string.watchlist_dialog_needed), true)) { // new item
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setTitle(R.string.watchlist_message);
|
||||
builder.setNegativeButton(R.string.return_to_homescreen,(dialog, which) -> {
|
||||
Intent i = new Intent(getActivity(), ConferencesActivity.class);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(i);
|
||||
getActivity().finish();
|
||||
});
|
||||
builder.setPositiveButton("OK",(dialog, which) -> {});
|
||||
SharedPreferences.Editor edit = preferences.edit();
|
||||
edit.putBoolean(getString(R.string.watchlist_dialog_needed), false);
|
||||
edit.commit();
|
||||
|
||||
builder.create().show();
|
||||
}
|
||||
} else if (action.getId() == REMOVE_WATCHLIST_ACTION) {
|
||||
if (mWatchlistItem != null) {
|
||||
mWatchlistItem.delete();
|
||||
}
|
||||
mRecordingActionsAdapter.replace(0, new Action(ADD_WATCHLIST_ACTION, EventsDetailsFragment.this.getString(R.string.add_to_watchlist)));
|
||||
} else {
|
||||
Intent i = new Intent(EventsDetailsFragment.this.getActivity(), PlayerActivity.class);
|
||||
i.putExtra(DetailsActivity.TYPE, eventType);
|
||||
if (eventType == DetailsActivity.TYPE_RECORDING) {
|
||||
i.putExtra(DetailsActivity.EVENT, mSelectedEvent);
|
||||
if (action.getId() == DUMMY_ID) {
|
||||
Recording dummy = new Recording();
|
||||
dummy.setRecordingUrl("https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8");
|
||||
dummy.setMimeType("video/hls");
|
||||
dummy.setLanguage("eng");
|
||||
dummy.setHighQuality(true);
|
||||
i.putExtra(DetailsActivity.RECORDING, dummy);
|
||||
} else {
|
||||
for (Recording r : mSelectedEvent.getRecordings()) {
|
||||
if (r.getApiID() == action.getId()) {
|
||||
i.putExtra(DetailsActivity.RECORDING, r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (eventType == DetailsActivity.TYPE_STREAM) {
|
||||
i.putExtra(DetailsActivity.ROOM, mRoom);
|
||||
StreamUrl streamUrl = EventsDetailsFragment.this.getStreamUrlForActionId((int) action.getId());
|
||||
if (streamUrl != null) {
|
||||
i.putExtra(DetailsActivity.STREAM_URL, streamUrl);
|
||||
} else {
|
||||
// TODO handle missing Stream
|
||||
return;
|
||||
}
|
||||
}
|
||||
EventsDetailsFragment.this.getActivity().startActivity(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.d(TAG, "onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
prepareBackgroundManager();
|
||||
final BrowseErrorFragment browseErrorFragment =
|
||||
BrowseErrorFragment.showErrorFragment(getFragmentManager(), FRAGMENT);
|
||||
eventType = getActivity().getIntent().getIntExtra(DetailsActivity.TYPE, -1);
|
||||
|
||||
if (eventType == DetailsActivity.TYPE_RECORDING) {
|
||||
mSelectedEvent = getActivity().getIntent()
|
||||
.getParcelableExtra(DetailsActivity.EVENT);
|
||||
mWatchlistItem = WatchlistItem.findById(WatchlistItem.class, mSelectedEvent.getApiID());
|
||||
} else if (eventType == DetailsActivity.TYPE_STREAM) {
|
||||
mRoom = getActivity().getIntent()
|
||||
.getParcelableExtra(DetailsActivity.ROOM);
|
||||
}
|
||||
|
||||
FullWidthDetailsOverviewRowPresenter mDetailsPresenter
|
||||
= setupDetailsOverviewRowPresenter();
|
||||
ClassPresenterSelector mPresenterSelector = new ClassPresenterSelector();
|
||||
mPresenterSelector.addClassPresenter(DetailsOverviewRow.class, mDetailsPresenter);
|
||||
mPresenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
|
||||
final ArrayObjectAdapter adapter = new ArrayObjectAdapter(mPresenterSelector);
|
||||
|
||||
((LeanbackBaseActivity) getActivity()).getApiServiceObservable()
|
||||
.doOnError(t -> browseErrorFragment.setErrorContent(t.getMessage()))
|
||||
.subscribe(mediaApiService -> {
|
||||
mMediaApiService = mediaApiService;
|
||||
if (eventType == DetailsActivity.TYPE_RECORDING) {
|
||||
final DetailsOverviewRow detailsOverviewRow = setupDetailsOverviewRow(mSelectedEvent);
|
||||
mediaApiService.getEvent(mSelectedEvent.getApiID())
|
||||
.doOnError(t -> browseErrorFragment.setErrorContent(t.getMessage()))
|
||||
.subscribe(event -> {
|
||||
mSelectedEvent = event;
|
||||
mRecordingActionsAdapter =
|
||||
getRecordingActionsAdapter(mSelectedEvent.getRecordings());
|
||||
detailsOverviewRow.setActionsAdapter(mRecordingActionsAdapter);
|
||||
adapter.add(detailsOverviewRow);
|
||||
mediaApiService.getConference(
|
||||
mSelectedEvent.getConferenceId())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(conference -> {
|
||||
String tag = null;
|
||||
if (mSelectedEvent.getTags().size() > 0) {
|
||||
tag = mSelectedEvent.getTags().get(0);
|
||||
List<Event> relatedEvents = conference.getEventsByTags().get(tag);
|
||||
relatedEvents.remove(mSelectedEvent);
|
||||
Collections.shuffle(relatedEvents);
|
||||
if (relatedEvents.size() > 5) {
|
||||
relatedEvents = relatedEvents.subList(0, NUM_RELATED_TALKS);
|
||||
}
|
||||
ArrayObjectAdapter relatedEventsAdapter
|
||||
= new ArrayObjectAdapter(new CardPresenter());
|
||||
relatedEventsAdapter.addAll(0, relatedEvents);
|
||||
HeaderItem header = new HeaderItem(getString(R.string.random_talks_on_this_track));
|
||||
adapter.add(new ListRow(header, relatedEventsAdapter));
|
||||
}
|
||||
|
||||
List<Event> selectedEvents = getRandomEvents(conference, tag);
|
||||
if (selectedEvents.size() > 0) {
|
||||
ArrayObjectAdapter randomEventAdapter
|
||||
= new ArrayObjectAdapter(new CardPresenter());
|
||||
randomEventAdapter.addAll(0, selectedEvents);
|
||||
HeaderItem header = new HeaderItem(getString(R.string.random_talks));
|
||||
adapter.add(new ListRow(header, randomEventAdapter));
|
||||
}
|
||||
|
||||
setOnItemViewClickedListener(new ItemViewClickedListener(this));
|
||||
setAdapter(adapter);
|
||||
browseErrorFragment.dismiss();
|
||||
});
|
||||
});
|
||||
} else if (eventType == DetailsActivity.TYPE_STREAM) {
|
||||
mediaApiService.getStreamingConferences()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(liveConferences -> {
|
||||
if(mRoom.getStreams() == null){
|
||||
mRoom = getRoom(mRoom, liveConferences);
|
||||
}
|
||||
final DetailsOverviewRow detailsOverviewRow = setupDetailsOverviewRow(mRoom);
|
||||
ArrayObjectAdapter actionsAdapter = getStreamActionsAdapter(mRoom.getStreams());
|
||||
detailsOverviewRow.setActionsAdapter(actionsAdapter);
|
||||
adapter.add(detailsOverviewRow);
|
||||
setOnItemViewClickedListener(new ItemViewClickedListener(EventsDetailsFragment.this));
|
||||
setAdapter(adapter);
|
||||
browseErrorFragment.dismiss();
|
||||
// TODO add other streams
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
startEntranceTransition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
mBackgroundmanager.release();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
private void prepareBackgroundManager() {
|
||||
mBackgroundmanager = BackgroundManager.getInstance(getActivity());
|
||||
mBackgroundmanager.attach(getActivity().getWindow());
|
||||
mMetrics = new DisplayMetrics();
|
||||
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
|
||||
}
|
||||
|
||||
private void updateBackground(String uri) {
|
||||
Glide.with(this)
|
||||
.load(uri)
|
||||
.asBitmap()
|
||||
.centerCrop()
|
||||
.into(new SimpleTarget<Bitmap>() {
|
||||
@Override
|
||||
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
|
||||
mBackgroundmanager.setBitmap(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Room getRoom(Room room, List<LiveConference> liveConferences) {
|
||||
for (LiveConference con : liveConferences) {
|
||||
for (Group g : con.getGroups()) {
|
||||
for (Room r : g.getRooms()) {
|
||||
if (r.getSlug().equals(room.getSlug())) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Event> getRandomEvents(Conference conference, String tag) {
|
||||
List<Event> randomEvents = conference.getEvents();
|
||||
Collections.shuffle(randomEvents);
|
||||
List<Event> selectedEvents;
|
||||
if (tag != null) {
|
||||
selectedEvents = new ArrayList<Event>();
|
||||
for (Event e : randomEvents) {
|
||||
if (!e.getTags().contains(tag)) {
|
||||
selectedEvents.add(e);
|
||||
}
|
||||
if (selectedEvents.size() == 5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
selectedEvents = randomEvents.subList(0, NUM_RANDOM_TALKS);
|
||||
}
|
||||
return selectedEvents;
|
||||
}
|
||||
|
||||
private FullWidthDetailsOverviewRowPresenter setupDetailsOverviewRowPresenter() {
|
||||
FullWidthDetailsOverviewRowPresenter mDetailsPresenter = new FullWidthDetailsOverviewRowPresenter(
|
||||
new EventDetailsDescriptionPresenter(getActivity()),
|
||||
new EventDetailsOverviewLogoPresenter());
|
||||
mDetailsPresenter.setBackgroundColor(getResources().getColor(R.color.selected_background));
|
||||
mDetailsPresenter.setInitialState(FullWidthDetailsOverviewRowPresenter.STATE_HALF);
|
||||
mDetailsPresenter.setAlignmentMode(FullWidthDetailsOverviewRowPresenter.ALIGN_MODE_START);
|
||||
|
||||
FullWidthDetailsOverviewSharedElementHelper helper
|
||||
= new FullWidthDetailsOverviewSharedElementHelper();
|
||||
helper.setSharedElementEnterTransition(getActivity(),
|
||||
EventDetailsActivity.SHARED_ELEMENT_NAME);
|
||||
mDetailsPresenter.setListener(helper);
|
||||
prepareEntranceTransition();
|
||||
|
||||
mDetailsPresenter.setOnActionClickedListener(mOnActionClickedListener);
|
||||
return mDetailsPresenter;
|
||||
}
|
||||
|
||||
private DetailsOverviewRow setupDetailsOverviewRow(Object event) {
|
||||
final DetailsOverviewRow row = new DetailsOverviewRow(event);
|
||||
String thumbUrl;
|
||||
if (event instanceof Event) {
|
||||
thumbUrl = ((Event) event).getThumbUrl();
|
||||
} else {
|
||||
thumbUrl = ((Room) event).getThumb();
|
||||
}
|
||||
Glide.with(getActivity())
|
||||
.load(thumbUrl)
|
||||
.asBitmap()
|
||||
.into(new SimpleTarget<Bitmap>(DETAIL_THUMB_WIDTH, DETAIL_THUMB_HEIGHT) {
|
||||
@Override
|
||||
public void onResourceReady(Bitmap resource,
|
||||
GlideAnimation<? super Bitmap> glideAnimation) {
|
||||
row.setImageBitmap(getActivity(), resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFailed(Exception e, Drawable errorDrawable) {
|
||||
// super.onLoadFailed(e, errorDrawable);
|
||||
row.setImageDrawable(getResources().getDrawable(DEFAULT_DRAWABLE));
|
||||
}
|
||||
});
|
||||
return row;
|
||||
}
|
||||
|
||||
private ArrayObjectAdapter getRecordingActionsAdapter(List<Recording> recordings) {
|
||||
ArrayObjectAdapter actionsAdapter = new ArrayObjectAdapter();
|
||||
if(mWatchlistItem != null){
|
||||
actionsAdapter.add(new Action(REMOVE_WATCHLIST_ACTION,getString(R.string.remove_from_watchlist)));
|
||||
} else {
|
||||
actionsAdapter.add(new Action(ADD_WATCHLIST_ACTION,getString(R.string.add_to_watchlist)));
|
||||
}
|
||||
if (recordings != null) {
|
||||
for (int i = 0; i < recordings.size(); i++) {
|
||||
Recording recording = recordings.get(i);
|
||||
if (recording.getMimeType().startsWith("video/") && !recording.getMimeType().equals("video/webm")) {
|
||||
String quality = recording.isHighQuality() ? "HD" : "SD";
|
||||
String title = quality + " (" + recording.getLanguage() + ")";
|
||||
actionsAdapter.add(new Action(recording.getApiID(), title, recording.getMimeType().substring(6)));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
actionsAdapter.add(new Action(DUMMY_ID, "Dummy", "HLS"));
|
||||
}
|
||||
return actionsAdapter;
|
||||
}
|
||||
|
||||
private ArrayObjectAdapter getStreamActionsAdapter(List<Stream> streams) {
|
||||
ArrayObjectAdapter actionsAdapter = new ArrayObjectAdapter();
|
||||
streamUrlList = new ArrayList<StreamUrl>();
|
||||
for (Stream s : streams) {
|
||||
if (s.getType().equals("video") || true)
|
||||
for (String key : s.getUrls().keySet()) {
|
||||
StreamUrl url = s.getUrls().get(key);
|
||||
int index = streamUrlList.size();
|
||||
streamUrlList.add(url);
|
||||
actionsAdapter.add(new Action(index, s.getDisplay(), url.getDisplay()));
|
||||
}
|
||||
}
|
||||
return actionsAdapter;
|
||||
}
|
||||
|
||||
private StreamUrl getStreamUrlForActionId(int actionId) {
|
||||
if (streamUrlList != null && streamUrlList.size() > actionId) {
|
||||
return streamUrlList.get(actionId);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class EventDetailsOverviewLogoPresenter extends DetailsOverviewLogoPresenter {
|
||||
static class ViewHolder extends DetailsOverviewLogoPresenter.ViewHolder {
|
||||
public ViewHolder(View view) {
|
||||
super(view);
|
||||
}
|
||||
|
||||
public FullWidthDetailsOverviewRowPresenter getParentPresenter() {
|
||||
return mParentPresenter;
|
||||
}
|
||||
|
||||
public FullWidthDetailsOverviewRowPresenter.ViewHolder getParentViewHolder() {
|
||||
return mParentViewHolder;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
ImageView imageView = (ImageView) LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.lb_fullwidth_details_overview_logo, parent, false);
|
||||
|
||||
Resources res = parent.getResources();
|
||||
int width = res.getDimensionPixelSize(R.dimen.detail_thumb_width);
|
||||
int height = res.getDimensionPixelSize(R.dimen.detail_thumb_height);
|
||||
imageView.setLayoutParams(new ViewGroup.MarginLayoutParams(width, height));
|
||||
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
|
||||
return new ViewHolder(imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
|
||||
DetailsOverviewRow row = (DetailsOverviewRow) item;
|
||||
ImageView imageView = ((ImageView) viewHolder.view);
|
||||
imageView.setImageDrawable(row.getImageDrawable());
|
||||
if (isBoundToImage((ViewHolder) viewHolder, row)) {
|
||||
EventDetailsOverviewLogoPresenter.ViewHolder vh =
|
||||
(EventDetailsOverviewLogoPresenter.ViewHolder) viewHolder;
|
||||
vh.getParentPresenter().notifyOnBindLogo(vh.getParentViewHolder());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,485 @@
|
|||
package de.nicidienase.chaosflix.leanback.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Bundle;
|
||||
import android.support.v17.leanback.app.PlaybackFragment;
|
||||
import android.support.v17.leanback.widget.ArrayObjectAdapter;
|
||||
import android.support.v17.leanback.widget.ClassPresenterSelector;
|
||||
import android.support.v17.leanback.widget.HeaderItem;
|
||||
import android.support.v17.leanback.widget.ListRow;
|
||||
import android.support.v17.leanback.widget.ListRowPresenter;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRow;
|
||||
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
|
||||
import android.support.v17.leanback.widget.Row;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import de.nicidienase.chaosflix.common.entities.PlaybackProgress;
|
||||
import de.nicidienase.chaosflix.leanback.CardPresenter;
|
||||
import de.nicidienase.chaosflix.leanback.ItemViewClickedListener;
|
||||
import de.nicidienase.chaosflix.leanback.PlaybackHelper;
|
||||
import de.nicidienase.chaosflix.R;
|
||||
import de.nicidienase.chaosflix.leanback.activities.LeanbackBaseActivity;
|
||||
import de.nicidienase.chaosflix.leanback.activities.DetailsActivity;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Event;
|
||||
import de.nicidienase.chaosflix.common.entities.recording.Recording;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.Room;
|
||||
import de.nicidienase.chaosflix.common.entities.streaming.StreamUrl;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS;
|
||||
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS;
|
||||
|
||||
/**
|
||||
* Created by felix on 26.03.17.
|
||||
*/
|
||||
|
||||
public class OverlayFragment extends PlaybackFragment {
|
||||
|
||||
private static final String TAG = OverlayFragment.class.getSimpleName();
|
||||
private static final long RESUME_SKIP = 5; // seconds to skip back on resume
|
||||
public static final int MAX_REMAINING = 30;
|
||||
|
||||
private Recording mSelectedRecording;
|
||||
private Event mSelectedEvent;
|
||||
private PlaybackProgress mPlaybackProgress;
|
||||
|
||||
private Room mSelectedRoom;
|
||||
private PlaybackHelper mHelper;
|
||||
private PlaybackControlListener mCallback;
|
||||
private ArrayObjectAdapter mRowsAdapter;
|
||||
private MediaSession mSession;
|
||||
private boolean mHasAudioFocus;
|
||||
private boolean mPauseTransient;
|
||||
private AudioManager mAudioManager;
|
||||
private MediaController mMediaControler;
|
||||
private int eventType;
|
||||
|
||||
private StreamUrl mSelectedStream;
|
||||
|
||||
private MediaController.Callback mMediaControllerCallback;
|
||||
private CompositeDisposable mDisposables = new CompositeDisposable();
|
||||
private final AudioManager.OnAudioFocusChangeListener mOnAudioFocusChangeListener =
|
||||
new AudioManager.OnAudioFocusChangeListener() {
|
||||
@Override
|
||||
public void onAudioFocusChange(int focusChange) {
|
||||
switch (focusChange) {
|
||||
case AudioManager.AUDIOFOCUS_LOSS:
|
||||
abandonAudioFocus();
|
||||
pause();
|
||||
break;
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
|
||||
if (mHelper.isMediaPlaying()) {
|
||||
pause();
|
||||
mPauseTransient = true;
|
||||
}
|
||||
break;
|
||||
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
|
||||
mCallback.mute(true);
|
||||
break;
|
||||
case AudioManager.AUDIOFOCUS_GAIN:
|
||||
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
|
||||
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
|
||||
if (mPauseTransient) {
|
||||
play();
|
||||
}
|
||||
mCallback.mute(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public interface PlaybackControlListener {
|
||||
void play();
|
||||
|
||||
void pause();
|
||||
|
||||
void playPause();
|
||||
|
||||
void setVideoSource(String source);
|
||||
|
||||
void skipForward(int sec);
|
||||
|
||||
void skipBackward(int sec);
|
||||
|
||||
void seekTo(long sec);
|
||||
|
||||
boolean isMediaPlaying();
|
||||
|
||||
long getLength();
|
||||
|
||||
long getCurrentPosition();
|
||||
|
||||
long getBufferedPosition();
|
||||
|
||||
void releasePlayer();
|
||||
|
||||
void mute(boolean state);
|
||||
|
||||
void nextAudioStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d(TAG, "OnCreate");
|
||||
|
||||
Intent intent = getActivity()
|
||||
.getIntent();
|
||||
eventType = intent.getIntExtra(DetailsActivity.TYPE, -1);
|
||||
if (eventType == DetailsActivity.TYPE_RECORDING) {
|
||||
mSelectedEvent = intent.getParcelableExtra(DetailsActivity.EVENT);
|
||||
mSelectedRecording = intent.getParcelableExtra(DetailsActivity.RECORDING);
|
||||
mHelper = new PlaybackHelper(getActivity(), this, mSelectedEvent, mSelectedRecording);
|
||||
|
||||
List<PlaybackProgress> progressList = PlaybackProgress.find(PlaybackProgress.class, "event_id = ?", String.valueOf(mSelectedEvent.getApiID()));
|
||||
if (progressList.size() > 0) {
|
||||
mPlaybackProgress = progressList.get(0);
|
||||
}
|
||||
} else if (eventType == DetailsActivity.TYPE_STREAM) {
|
||||
mSelectedRoom = intent.getParcelableExtra(DetailsActivity.ROOM);
|
||||
mSelectedStream = intent.getParcelableExtra(DetailsActivity.STREAM_URL);
|
||||
mHelper = new PlaybackHelper(getActivity(), this, mSelectedRoom, mSelectedStream);
|
||||
} else {
|
||||
Log.d(TAG, "No Media found, finishing");
|
||||
getActivity().finish();
|
||||
}
|
||||
mAudioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
|
||||
|
||||
setBackgroundType(PlaybackFragment.BG_LIGHT);
|
||||
setFadingEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onStart() {
|
||||
super.onStart();
|
||||
Log.d(TAG, "OnStart");
|
||||
|
||||
PlaybackControlsRowPresenter playbackControlsRowPresenter = mHelper.createControlsRowAndPresenter();
|
||||
PlaybackControlsRow controlsRow = mHelper.getControlsRow();
|
||||
mMediaControllerCallback = mHelper.createMediaControllerCallback();
|
||||
requestAudioFocus();
|
||||
mMediaControler = getActivity().getMediaController();
|
||||
if (mMediaControler != null) {
|
||||
mMediaControler.registerCallback(mMediaControllerCallback);
|
||||
} else {
|
||||
Log.d(TAG, "MediaController is null");
|
||||
getActivity().finish();
|
||||
}
|
||||
ClassPresenterSelector ps = new ClassPresenterSelector();
|
||||
ps.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter);
|
||||
ps.addClassPresenter(ListRow.class, new ListRowPresenter());
|
||||
mRowsAdapter = new ArrayObjectAdapter(ps);
|
||||
mRowsAdapter.add(controlsRow);
|
||||
if (eventType == DetailsActivity.TYPE_RECORDING) {
|
||||
if (mSelectedEvent.getMetadata() != null && mSelectedEvent.getMetadata().getRelated() != null) {
|
||||
mRowsAdapter.add(getRelatedItems());
|
||||
setOnItemViewClickedListener(new ItemViewClickedListener(this));
|
||||
}
|
||||
} else if (eventType == DetailsActivity.TYPE_STREAM) {
|
||||
// TODO add other streams as related events
|
||||
}
|
||||
setAdapter(mRowsAdapter);
|
||||
|
||||
if (mCallback != null && eventType == DetailsActivity.TYPE_STREAM) {
|
||||
mCallback.setVideoSource(mSelectedStream.getUrl());
|
||||
} else if (mCallback != null && eventType == DetailsActivity.TYPE_RECORDING) {
|
||||
mCallback.setVideoSource(mSelectedRecording.getRecordingUrl());
|
||||
} else {
|
||||
Log.d(TAG, "Callback not set or not event/stream");
|
||||
}
|
||||
requestAudioFocus();
|
||||
if (mPlaybackProgress != null) {
|
||||
showContinueOrRestartDialog();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
}
|
||||
|
||||
private void showContinueOrRestartDialog() {
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(getActivity())
|
||||
.setMessage(R.string.resume_question)
|
||||
.setPositiveButton(R.string.start_again, (dialog, which) -> play())
|
||||
.setNegativeButton(R.string.resume, (dialog, which) -> {
|
||||
mCallback.seekTo(getProgress());
|
||||
play();
|
||||
})
|
||||
.create();
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
private long getProgress() {
|
||||
long progress = mPlaybackProgress.getProgress() / 1000;
|
||||
if (progress >= mSelectedEvent.getLength()) {
|
||||
return 0;
|
||||
} else {
|
||||
return Math.max(0, progress - RESUME_SKIP);
|
||||
}
|
||||
}
|
||||
|
||||
private Row getRelatedItems() {
|
||||
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
|
||||
final Set<Long> related = mSelectedEvent.getMetadata().getRelated().keySet();
|
||||
mDisposables.add(((LeanbackBaseActivity) getActivity()).getApiServiceObservable()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
mediaApiService -> {
|
||||
for (long id : related) {
|
||||
mDisposables.add(mediaApiService.getEvent(id)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(event -> listRowAdapter.add(event)));
|
||||
}
|
||||
listRowAdapter.notifyArrayItemRangeChanged(0, listRowAdapter.size());
|
||||
}
|
||||
)
|
||||
);
|
||||
HeaderItem header = new HeaderItem(0, getString(R.string.related_talks));
|
||||
return new ListRow(header, listRowAdapter);
|
||||
}
|
||||
|
||||
public boolean isMediaPlaying() {
|
||||
if (mCallback != null) {
|
||||
return mCallback.isMediaPlaying();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getCurrentPosition() {
|
||||
if (eventType == DetailsActivity.TYPE_RECORDING) {
|
||||
if (mCallback != null) {
|
||||
return (int) mCallback.getCurrentPosition();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private long getCurrentPositionLong() {
|
||||
if (eventType == DetailsActivity.TYPE_RECORDING) {
|
||||
if (mCallback != null) {
|
||||
return mCallback.getCurrentPosition();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getCurrentBufferedPosition() {
|
||||
if (mCallback != null) {
|
||||
return mCallback.getBufferedPosition();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (mSession != null) {
|
||||
mSession.release();
|
||||
}
|
||||
mDisposables.dispose();
|
||||
abandonAudioFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (mSelectedEvent != null) {
|
||||
if (mPlaybackProgress != null) {
|
||||
if ((mSelectedEvent.getLength() - mCallback.getCurrentPosition() / 1000) > MAX_REMAINING) {
|
||||
mPlaybackProgress.setProgress(mCallback.getCurrentPosition());
|
||||
mPlaybackProgress.save();
|
||||
} else {
|
||||
mPlaybackProgress.delete();
|
||||
}
|
||||
} else if ((mSelectedEvent.getLength() - mCallback.getCurrentPosition() / 1000) > MAX_REMAINING) {
|
||||
mPlaybackProgress = new PlaybackProgress(mSelectedEvent.getApiID(),
|
||||
mCallback.getCurrentPosition(), mSelectedRecording.getApiID());
|
||||
mPlaybackProgress.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (mSession != null) {
|
||||
mSession.release();
|
||||
}
|
||||
mCallback.releasePlayer();
|
||||
mHelper.onStop();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
setupMediaSession(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
setupMediaSession(activity);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WrongConstant")
|
||||
private void setupMediaSession(Context context) {
|
||||
Log.d(TAG, "OnAttach");
|
||||
if (context instanceof PlaybackControlListener) {
|
||||
mCallback = (PlaybackControlListener) context;
|
||||
} else {
|
||||
throw (new RuntimeException("Activity must implement PlaybackControlListener"));
|
||||
}
|
||||
|
||||
if (mSession == null) {
|
||||
mSession = new MediaSession(getActivity(), "chaosflix");
|
||||
mSession.setCallback(new ChaosflixSessionCallback());
|
||||
mSession.setFlags(FLAG_HANDLES_MEDIA_BUTTONS | FLAG_HANDLES_TRANSPORT_CONTROLS);
|
||||
mSession.setActive(true);
|
||||
|
||||
setPlaybackState(PlaybackState.STATE_NONE);
|
||||
|
||||
getActivity().setMediaController(
|
||||
new MediaController(getActivity(), mSession.getSessionToken()));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WrongConstant")
|
||||
private void setPlaybackState(int state) {
|
||||
long currentPosition = getCurrentPositionLong();
|
||||
PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
|
||||
.setActions(getAvailableActions(state))
|
||||
.setState(PlaybackState.STATE_PLAYING, currentPosition, 0);
|
||||
|
||||
if (mSession != null) {
|
||||
mSession.setPlaybackState(stateBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
private int getPlaybackState() {
|
||||
Activity activity = getActivity();
|
||||
|
||||
if (activity != null) {
|
||||
PlaybackState state = activity.getMediaController().getPlaybackState();
|
||||
if (state != null) {
|
||||
return state.getState();
|
||||
} else {
|
||||
return PlaybackState.STATE_NONE;
|
||||
}
|
||||
}
|
||||
return PlaybackState.STATE_NONE;
|
||||
}
|
||||
|
||||
private long getAvailableActions(int nextState) {
|
||||
long actions = PlaybackState.ACTION_PLAY |
|
||||
PlaybackState.ACTION_SKIP_TO_NEXT |
|
||||
PlaybackState.ACTION_SKIP_TO_PREVIOUS |
|
||||
PlaybackState.ACTION_FAST_FORWARD |
|
||||
PlaybackState.ACTION_REWIND |
|
||||
PlaybackState.ACTION_PAUSE;
|
||||
|
||||
if (nextState == PlaybackState.STATE_PLAYING) {
|
||||
actions |= PlaybackState.ACTION_PAUSE;
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private void requestAudioFocus() {
|
||||
if (mHasAudioFocus) {
|
||||
return;
|
||||
}
|
||||
int result = mAudioManager.requestAudioFocus(mOnAudioFocusChangeListener,
|
||||
AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
|
||||
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||
mHasAudioFocus = true;
|
||||
} else {
|
||||
pause();
|
||||
}
|
||||
}
|
||||
|
||||
private void abandonAudioFocus() {
|
||||
mHasAudioFocus = false;
|
||||
mAudioManager.abandonAudioFocus(mOnAudioFocusChangeListener);
|
||||
}
|
||||
|
||||
private void play() {
|
||||
if (mCallback != null) {
|
||||
setPlaybackState(PlaybackState.STATE_PLAYING);
|
||||
setFadingEnabled(true);
|
||||
mCallback.play();
|
||||
}
|
||||
}
|
||||
|
||||
private void pause() {
|
||||
if (mCallback != null) {
|
||||
setPlaybackState(PlaybackState.STATE_PAUSED);
|
||||
setFadingEnabled(false);
|
||||
mCallback.pause();
|
||||
}
|
||||
}
|
||||
|
||||
private void rewind() {
|
||||
if (mCallback != null) {
|
||||
int prevState = getPlaybackState();
|
||||
setPlaybackState(PlaybackState.STATE_FAST_FORWARDING);
|
||||
mCallback.skipBackward(30);
|
||||
setPlaybackState(prevState);
|
||||
}
|
||||
}
|
||||
|
||||
private void fastForward() {
|
||||
if (mCallback != null) {
|
||||
int prevState = getPlaybackState();
|
||||
setPlaybackState(PlaybackState.STATE_FAST_FORWARDING);
|
||||
mCallback.skipForward(30);
|
||||
setPlaybackState(prevState);
|
||||
}
|
||||
}
|
||||
|
||||
private class ChaosflixSessionCallback extends MediaSession.Callback {
|
||||
@Override
|
||||
public void onPlay() {
|
||||
play();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
pause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFastForward() {
|
||||
fastForward();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRewind() {
|
||||
rewind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSkipToNext() {
|
||||
mCallback.skipForward(5 * 60);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSkipToPrevious() {
|
||||
mCallback.skipBackward(5 * 60);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSeekTo(long pos) {
|
||||
mCallback.seekTo(pos);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:name="de.nicidienase.chaosflix.leanback.fragments.ConferencesBrowseFragment"
|
||||
android:id="@+id/browse_fragment"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:name="de.nicidienase.chaosflix.leanback.fragments.ConferencesGridFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
9
leanback/src/main/res/layout/activity_event_details.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<fragment android:id="@+id/details_fragment"
|
||||
android:name="de.nicidienase.chaosflix.leanback.fragments.EventsDetailsFragment"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".leanback.activities.DetailsActivity"
|
||||
tools:deviceIds="tv"/>
|
10
leanback/src/main/res/layout/activity_events_browse.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<fragment android:id="@+id/browse_fragment"
|
||||
android:name="de.nicidienase.chaosflix.leanback.fragments.EventsBrowseFragment"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".leanback.activities.EventsActivity"
|
||||
tools:deviceIds="tv"
|
||||
tools:ignore="MergeRootFrame"/>
|
27
leanback/src/main/res/layout/activity_playback.xml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/videoView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<fragment
|
||||
android:name="de.nicidienase.chaosflix.leanback.fragments.OverlayFragment"
|
||||
android:id="@+id/playback_controls_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading_player"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal|center"
|
||||
android:indeterminate="true"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
</FrameLayout>
|
38
leanback/src/main/res/layout/detail_view_content.xml
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_text"
|
||||
style="@style/Widget.Leanback.DetailsDescriptionTitleStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="3dp"
|
||||
android:layout_marginTop="15dp"
|
||||
android:fontFamily="sans-serif-light"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle_text"
|
||||
style="@style/Widget.Leanback.DetailsDescriptionSubtitleStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="16dp"
|
||||
android:fontFamily="sans-serif-condensed"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/speakers_text"
|
||||
style="@style/Widget.Leanback.DetailsDescriptionSubtitleStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="16dp"
|
||||
android:fontFamily="sans-serif-condensed"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description_text"
|
||||
style="@style/Widget.Leanback.DetailsDescriptionBodyStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-regular"/>
|
||||
</LinearLayout>
|
18
leanback/src/main/res/layout/header_item_layout.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/header_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/header_label"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
6
leanback/src/main/res/values/styles.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="@style/Theme.Leanback">
|
||||
</style>
|
||||
</resources>
|
14
leanback/src/main/res/values/themes.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<resources>
|
||||
|
||||
<style name="Theme.Example.Leanback" parent="Theme.Leanback">
|
||||
<item name="android:windowEnterTransition">@android:transition/fade</item>
|
||||
<item name="android:windowExitTransition">@android:transition/fade</item>
|
||||
<item name="android:windowSharedElementExitTransition">@android:transition/move</item>
|
||||
<item name="android:windowSharedElementEnterTransition">@android:transition/move</item>
|
||||
<item name="android:windowAllowReturnTransitionOverlap">true</item>
|
||||
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
||||
<item name="android:windowContentTransitions">true</item>
|
||||
<item name="android:colorPrimary">@color/search_opaque</item>
|
||||
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,31 @@
|
|||
package de.nicidienase.chaosflix;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.support.multidex.MultiDex;
|
||||
|
||||
import com.orm.SugarContext;
|
||||
|
||||
/**
|
||||
* Created by felix on 18.03.17.
|
||||
*/
|
||||
|
||||
public class ChaosflixApplication extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
SugarContext.init(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
SugarContext.terminate();
|
||||
super.onTerminate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
MultiDex.install(base);
|
||||
super.attachBaseContext(base);
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 399 KiB After Width: | Height: | Size: 399 KiB |
Before Width: | Height: | Size: 633 KiB After Width: | Height: | Size: 633 KiB |
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 138 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 418 KiB After Width: | Height: | Size: 418 KiB |
Before Width: | Height: | Size: 434 KiB After Width: | Height: | Size: 434 KiB |