.
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
6
.idea/compiler.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
19
.idea/gradle.xml
generated
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
52
.idea/misc.xml
generated
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DesignSurface">
|
||||
<option name="filePathToZoomLevelMap">
|
||||
<map>
|
||||
<entry key="..\:/code/VlcJellyfin/app/src/main/res/drawable/ic_outline_play_circle_outline_128.xml" value="0.1555" />
|
||||
<entry key="..\:/code/VlcJellyfin/app/src/main/res/drawable/played_percentage.xml" value="0.1765" />
|
||||
<entry key="..\:/code/VlcJellyfin/app/src/main/res/drawable/popmenu_focus.xml" value="0.1555" />
|
||||
<entry key="..\:/code/VlcJellyfin/app/src/main/res/drawable/shape_user_focus_vholder.xml" value="0.1555" />
|
||||
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/activity_detail.xml" value="0.14583333333333334" />
|
||||
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/activity_search.xml" value="0.14583333333333334" />
|
||||
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/activity_vlc_player.xml" value="0.286231884057971" />
|
||||
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/dialog_login.xml" value="0.14479166666666668" />
|
||||
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/item_h.xml" value="0.16770833333333332" />
|
||||
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/item_v.xml" value="0.14583333333333334" />
|
||||
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/popmenu.xml" value="0.14479166666666668" />
|
||||
<entry key="..\:/code/VlcJellyfin/app/src/main/res/layout/popmenu_item.xml" value="0.14479166666666668" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/drawable/img_loading_placeholder.xml" value="0.1625" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/drawable/played_percentage.xml" value="0.1615" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/drawable/popmenu_focus.xml" value="0.1625" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/drawable/shape_user_focus.xml" value="0.1625" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/drawable/shape_user_focus_VH.xml" value="0.1625" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/drawable/shape_user_focus_vholder.xml" value="0.1625" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/activebar_custom.xml" value="0.14895833333333333" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/activity_collection.xml" value="0.14895833333333333" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/activity_detail.xml" value="0.1" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/activity_main.xml" value="0.2957427536231884" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/activity_search.xml" value="0.14791666666666667" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/activity_vlc_player.xml" value="0.14895833333333333" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/dialog_login.xml" value="0.4" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/home_horizon_tvrecycler.xml" value="0.14791666666666667" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/item_h.xml" value="0.14895833333333333" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/item_title.xml" value="0.14895833333333333" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/item_v.xml" value="0.14895833333333333" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/jellyfin_home.xml" value="0.14895833333333333" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/loading.xml" value="0.2957427536231884" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/loading_alert.xml" value="0.14895833333333333" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/menu_item.xml" value="0.2957427536231884" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/popmenu.xml" value="0.14895833333333333" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/layout/popmenu_item.xml" value="0.14895833333333333" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/menu/activebar_menu.xml" value="0.14895833333333333" />
|
||||
<entry key="..\:/work/VlcJellyfin/app/src/main/res/menu/activebar_sort_menu.xml" value="0.14895833333333333" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
5
README.md
Normal file
@ -0,0 +1,5 @@
|
||||
jellyfin安卓TV客户端,支持安卓4.2以上。
|
||||
|
||||
在安卓5.1及以下环境中运行时,集成的VLC播放器可能无法播放HTTPS,可在右上角菜单勾选"调用外部播放器"选项,使用外部播放器播放。
|
||||
|
||||
支持jellyfin8.1以上
|
1
app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
53
app/build.gradle
Normal file
@ -0,0 +1,53 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 32
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.sifacai.vlcjellyfin"
|
||||
minSdk 17
|
||||
targetSdk 32
|
||||
versionCode 1
|
||||
versionName "1.0.5"
|
||||
|
||||
multiDexEnabled true
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
enable true
|
||||
include 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
//implementation files('libs/libvlc-all-3.4.9.aar')
|
||||
implementation 'org.videolan.android:libvlc-all:3.5.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.5.0'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
implementation 'com.lzy.net:okgo:3.0.4'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
implementation 'com.google.code.gson:gson:2.9.1'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
|
||||
implementation 'com.owen:tv-recyclerview:3.0.0'
|
||||
implementation 'com.squareup.picasso:picasso:2.8'
|
||||
implementation 'com.github.JessYanCoding:AndroidAutoSize:v1.2.1'
|
||||
}
|
21
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# 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
|
@ -0,0 +1,26 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("org.sifacai.vlcjellyfin", appContext.getPackageName());
|
||||
}
|
||||
}
|
53
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.sifacai.vlcjellyfin">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@drawable/touchicon144"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@drawable/touchicon144"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/jellyfin"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:hardwareAccelerated="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".HomeActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".DetailActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".CollectionActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".SearchActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".VlcPlayerActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.AppCompat.NoActionBar"/>
|
||||
|
||||
<meta-data
|
||||
android:name="design_width_in_dp"
|
||||
android:value="1280" />
|
||||
<meta-data
|
||||
android:name="design_height_in_dp"
|
||||
android:value="720" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
236
app/src/main/java/org/sifacai/vlcjellyfin/BaseActivity.java
Normal file
@ -0,0 +1,236 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import me.jessyan.autosize.internal.CustomAdapt;
|
||||
|
||||
public class BaseActivity extends AppCompatActivity implements CustomAdapt {
|
||||
public AppCompatActivity mAA = this;
|
||||
private ProgressDialog progressDialog;
|
||||
private ImageView activeBarBack;
|
||||
private ImageView actionBarSetBtn;
|
||||
private PopupMenu settingMenu;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (null != actionBar) {
|
||||
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
|
||||
actionBar.setCustomView(R.layout.activebar_custom);
|
||||
|
||||
activeBarBack = findViewById(R.id.actionBar_back);
|
||||
activeBarBack.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
mAA.finish();
|
||||
}
|
||||
});
|
||||
|
||||
ImageView activeBarSearch = findViewById(R.id.actionBar_searchBtn);
|
||||
activeBarSearch.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(mAA,SearchActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
//actionBar设置菜单
|
||||
actionBarSetBtn = findViewById(R.id.actionBar_setBtn);
|
||||
actionBarSetBtn.setOnClickListener(actionBarSetBtnOnclick);
|
||||
settingMenu = new PopupMenu(this,actionBarSetBtn);
|
||||
settingMenu.getMenuInflater().inflate(R.menu.activebar_menu,settingMenu.getMenu());
|
||||
settingMenu.setOnMenuItemClickListener(settingMenuItemOnclick);
|
||||
}
|
||||
}
|
||||
|
||||
private View.OnClickListener actionBarSetBtnOnclick = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
settingMenu.show();
|
||||
Menu setmenu = settingMenu.getMenu();
|
||||
setmenu.findItem(R.id.actionBar_option_PlayStartInBegin).setChecked(JfClient.config.isPlayStartInBegin());
|
||||
setmenu.findItem(R.id.actionBar_option_HAACC).setChecked(JfClient.config.isHAACC());
|
||||
setmenu.findItem(R.id.actionBar_option_FORCE_HAACC).setChecked(JfClient.config.isFORCE_HAACC());
|
||||
setmenu.findItem(R.id.actionBar_option_ExtensionPlayer).setChecked(JfClient.config.isExtensionPlayer());
|
||||
}
|
||||
};
|
||||
|
||||
private PopupMenu.OnMenuItemClickListener settingMenuItemOnclick = new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
int menuid = menuItem.getItemId();
|
||||
switch (menuid){
|
||||
case R.id.activeBar_option_logout:
|
||||
logout();
|
||||
break;
|
||||
case R.id.actionBar_option_HAACC:
|
||||
JfClient.config.setHAACC(!JfClient.config.isHAACC());
|
||||
break;
|
||||
case R.id.actionBar_option_FORCE_HAACC:
|
||||
JfClient.config.setFORCE_HAACC(!JfClient.config.isFORCE_HAACC());
|
||||
break;
|
||||
case R.id.actionBar_option_PlayStartInBegin:
|
||||
JfClient.config.setPlayStartInBegin(!JfClient.config.isPlayStartInBegin());
|
||||
break;
|
||||
case R.id.actionBar_option_ExtensionPlayer:
|
||||
JfClient.config.setExtensionPlayer(!JfClient.config.isExtensionPlayer());
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// @Override
|
||||
// public boolean onCreateOptionsMenu(@NonNull Menu menu) {
|
||||
// getMenuInflater().inflate(R.menu.activebar_menu, menu);
|
||||
// return super.onCreateOptionsMenu(menu);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
// if (item.getItemId() == R.id.activeBar_option_logout) {
|
||||
// logout();
|
||||
// }
|
||||
// return super.onOptionsItemSelected(item);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 禁用标题栏返回按钮
|
||||
*/
|
||||
public void disableActiveBarBack() {
|
||||
if(null != activeBarBack)
|
||||
activeBarBack.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否按照宽度进行等比例适配 (为了保证在高宽比不同的屏幕上也能正常适配, 所以只能在宽度和高度之中选择一个作为基准进行适配)
|
||||
*
|
||||
* @return {@code true} 为按照宽度进行适配, {@code false} 为按照高度进行适配
|
||||
*/
|
||||
@Override
|
||||
public boolean isBaseOnWidth() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回设计图上的设计尺寸
|
||||
* {@link #getSizeInDp} 须配合 {@link #isBaseOnWidth()} 使用, 规则如下:
|
||||
* 如果 {@link #isBaseOnWidth()} 返回 {@code true}, {@link #getSizeInDp} 则应该返回设计图的总宽度
|
||||
* 如果 {@link #isBaseOnWidth()} 返回 {@code false}, {@link #getSizeInDp} 则应该返回设计图的总高度
|
||||
* 如果您不需要自定义设计图上的设计尺寸, 想继续使用在 AndroidManifest 中填写的设计图尺寸, {@link #getSizeInDp} 则返回 {@code 0}
|
||||
*
|
||||
* @return 设计图上的设计尺寸
|
||||
*/
|
||||
@Override
|
||||
public float getSizeInDp() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void showLoadingDialog() {
|
||||
showLoadingDialog("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示加载动画框
|
||||
*/
|
||||
public void showLoadingDialog(String title) {
|
||||
mAA.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if(null != progressDialog){
|
||||
if(progressDialog.isShowing()){
|
||||
progressDialog.dismiss();
|
||||
}
|
||||
progressDialog = null;
|
||||
}
|
||||
progressDialog = new ProgressDialog(mAA);
|
||||
progressDialog.setMessage(title);
|
||||
progressDialog.show();
|
||||
progressDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(DialogInterface dialogInterface, int i, KeyEvent keyEvent) {
|
||||
if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) {
|
||||
dismissLoadingDialog();
|
||||
mAA.finish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏加载框
|
||||
*/
|
||||
public void dismissLoadingDialog() {
|
||||
mAA.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (null != progressDialog && progressDialog.isShowing()) {
|
||||
progressDialog.dismiss();
|
||||
progressDialog = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置加载框文字
|
||||
*
|
||||
* @param text
|
||||
*/
|
||||
public void setLoadingText(String text) {
|
||||
mAA.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (null != progressDialog && progressDialog.isShowing()) {
|
||||
progressDialog.setMessage(text);
|
||||
}else{
|
||||
showLoadingDialog(text);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void ShowToask(String msg) {
|
||||
mAA.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(mAA, msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 登 出
|
||||
*/
|
||||
private void logout() {
|
||||
JfClient.config.clear();
|
||||
JfClient.UserId = "";
|
||||
JfClient.AccessToken = "";
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
39
app/src/main/java/org/sifacai/vlcjellyfin/Bean/Chapters.java
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright 2022 json.cn
|
||||
*/
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Auto-generated: 2022-09-01 14:6:30
|
||||
*
|
||||
* @author json.cn (i@json.cn)
|
||||
* @website http://www.json.cn/java2pojo/
|
||||
*/
|
||||
public class Chapters {
|
||||
|
||||
private long StartPositionTicks;
|
||||
private String Name;
|
||||
private String ImageDateModified;
|
||||
public void setStartPositionTicks(long StartPositionTicks) {
|
||||
this.StartPositionTicks = StartPositionTicks;
|
||||
}
|
||||
public long getStartPositionTicks() {
|
||||
return StartPositionTicks;
|
||||
}
|
||||
|
||||
public void setName(String Name) {
|
||||
this.Name = Name;
|
||||
}
|
||||
public String getName() {
|
||||
return Name;
|
||||
}
|
||||
|
||||
public void setImageDateModified(String ImageDateModified) {
|
||||
this.ImageDateModified = ImageDateModified;
|
||||
}
|
||||
public String getImageDateModified() {
|
||||
return ImageDateModified;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright 2022 json.cn
|
||||
*/
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
|
||||
/**
|
||||
* Auto-generated: 2022-09-01 14:6:30
|
||||
*
|
||||
* @author json.cn (i@json.cn)
|
||||
* @website http://www.json.cn/java2pojo/
|
||||
*/
|
||||
public class ExternalUrls {
|
||||
|
||||
private String Name;
|
||||
private String Url;
|
||||
public void setName(String Name) {
|
||||
this.Name = Name;
|
||||
}
|
||||
public String getName() {
|
||||
return Name;
|
||||
}
|
||||
|
||||
public void setUrl(String Url) {
|
||||
this.Url = Url;
|
||||
}
|
||||
public String getUrl() {
|
||||
return Url;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright 2022 json.cn
|
||||
*/
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
|
||||
/**
|
||||
* Auto-generated: 2022-09-01 14:6:30
|
||||
*
|
||||
* @author json.cn (i@json.cn)
|
||||
* @website http://www.json.cn/java2pojo/
|
||||
*/
|
||||
public class GenreItems {
|
||||
|
||||
private String Name;
|
||||
private String Id;
|
||||
public void setName(String Name) {
|
||||
this.Name = Name;
|
||||
}
|
||||
public String getName() {
|
||||
return Name;
|
||||
}
|
||||
|
||||
public void setId(String Id) {
|
||||
this.Id = Id;
|
||||
}
|
||||
public String getId() {
|
||||
return Id;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright 2022 json.cn
|
||||
*/
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
|
||||
/**
|
||||
* Auto-generated: 2022-09-01 14:6:30
|
||||
*
|
||||
* @author json.cn (i@json.cn)
|
||||
* @website http://www.json.cn/java2pojo/
|
||||
*/
|
||||
public class ImageTags {
|
||||
|
||||
private String Primary;
|
||||
private String Logo;
|
||||
private String Thumb;
|
||||
public void setPrimary(String Primary) {
|
||||
this.Primary = Primary;
|
||||
}
|
||||
public String getPrimary() {
|
||||
return Primary;
|
||||
}
|
||||
|
||||
public void setLogo(String Logo) {
|
||||
this.Logo = Logo;
|
||||
}
|
||||
public String getLogo() {
|
||||
return Logo;
|
||||
}
|
||||
|
||||
public void setThumb(String thumb) {
|
||||
Thumb = thumb;
|
||||
}
|
||||
public String getThumb() {
|
||||
return Thumb;
|
||||
}
|
||||
|
||||
|
||||
}
|
502
app/src/main/java/org/sifacai/vlcjellyfin/Bean/Item.java
Normal file
@ -0,0 +1,502 @@
|
||||
/**
|
||||
* Copyright 2022 json.cn
|
||||
*/
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Auto-generated: 2022-09-01 14:6:30
|
||||
*
|
||||
* @author json.cn (i@json.cn)
|
||||
* @website http://www.json.cn/java2pojo/
|
||||
*/
|
||||
public class Item {
|
||||
|
||||
private String Name;
|
||||
private String OriginalTitle;
|
||||
private String ServerId;
|
||||
private String Id;
|
||||
private String Etag;
|
||||
private Date DateCreated;
|
||||
private boolean CanDelete;
|
||||
private boolean CanDownload;
|
||||
private boolean HasSubtitles;
|
||||
private String Container;
|
||||
private String SortName;
|
||||
private String PremiereDate;
|
||||
private List<ExternalUrls> ExternalUrls;
|
||||
private List<MediaSources> MediaSources;
|
||||
private List<String> ProductionLocations;
|
||||
private String Path;
|
||||
private boolean EnableMediaSourceDisplay;
|
||||
private String OfficialRating;
|
||||
private String ChannelId;
|
||||
private String Overview;
|
||||
private List<String> Taglines;
|
||||
private List<String> Genres;
|
||||
private String CommunityRating;
|
||||
private long RunTimeTicks;
|
||||
private String PlayAccess;
|
||||
private String ProductionYear;
|
||||
// private List<String> RemoteTrailers;
|
||||
private ProviderIds ProviderIds;
|
||||
private boolean IsHD;
|
||||
private boolean IsFolder;
|
||||
private String ParentId;
|
||||
private String Type;
|
||||
private List<People> People;
|
||||
private List<Studios> Studios;
|
||||
private List<GenreItems> GenreItems;
|
||||
private int LocalTrailerCount;
|
||||
private UserData UserData;
|
||||
private int SpecialFeatureCount;
|
||||
private String DisplayPreferencesId;
|
||||
private List<String> Tags;
|
||||
private double PrimaryImageAspectRatio;
|
||||
private List<MediaStreams> MediaStreams;
|
||||
private String VideoType;
|
||||
private ImageTags ImageTags;
|
||||
private List<String> BackdropImageTags;
|
||||
private List<Chapters> Chapters;
|
||||
private String LocationType;
|
||||
private String MediaType;
|
||||
private List<String> LockedFields;
|
||||
private boolean LockData;
|
||||
private int Width;
|
||||
private int Height;
|
||||
|
||||
public int getPartCount() {
|
||||
return PartCount;
|
||||
}
|
||||
|
||||
public void setPartCount(int partCount) {
|
||||
PartCount = partCount;
|
||||
}
|
||||
|
||||
private int PartCount;
|
||||
private String SeriesId;
|
||||
private String SeriesName;
|
||||
private String SeasonId;
|
||||
//private List<String> ProductionLocations;
|
||||
//private
|
||||
|
||||
public String getSeriesId() {
|
||||
return SeriesId;
|
||||
}
|
||||
|
||||
public void setSeriesId(String seriesId) {
|
||||
SeriesId = seriesId;
|
||||
}
|
||||
|
||||
public String getSeasonId() {
|
||||
return SeasonId;
|
||||
}
|
||||
|
||||
public void setSeasonId(String seasonId) {
|
||||
SeasonId = seasonId;
|
||||
}
|
||||
|
||||
private String SeasonName;
|
||||
|
||||
public boolean isHasSubtitles() {
|
||||
return HasSubtitles;
|
||||
}
|
||||
|
||||
public boolean isHD() {
|
||||
return IsHD;
|
||||
}
|
||||
|
||||
public void setHD(boolean HD) {
|
||||
IsHD = HD;
|
||||
}
|
||||
|
||||
public boolean isFolder() {
|
||||
return IsFolder;
|
||||
}
|
||||
|
||||
public void setFolder(boolean folder) {
|
||||
IsFolder = folder;
|
||||
}
|
||||
|
||||
public String getSeriesName() {
|
||||
return SeriesName;
|
||||
}
|
||||
|
||||
public void setSeriesName(String seriesName) {
|
||||
SeriesName = seriesName;
|
||||
}
|
||||
|
||||
public String getSeasonName() {
|
||||
return SeasonName;
|
||||
}
|
||||
|
||||
public void setSeasonName(String seasonName) {
|
||||
SeasonName = seasonName;
|
||||
}
|
||||
|
||||
public void setName(String Name) {
|
||||
this.Name = Name;
|
||||
}
|
||||
public String getName() {
|
||||
return Name;
|
||||
}
|
||||
|
||||
public void setOriginalTitle(String OriginalTitle) {
|
||||
this.OriginalTitle = OriginalTitle;
|
||||
}
|
||||
public String getOriginalTitle() {
|
||||
return OriginalTitle;
|
||||
}
|
||||
|
||||
public void setServerId(String ServerId) {
|
||||
this.ServerId = ServerId;
|
||||
}
|
||||
public String getServerId() {
|
||||
return ServerId;
|
||||
}
|
||||
|
||||
public void setId(String Id) {
|
||||
this.Id = Id;
|
||||
}
|
||||
public String getId() {
|
||||
return Id;
|
||||
}
|
||||
|
||||
public void setEtag(String Etag) {
|
||||
this.Etag = Etag;
|
||||
}
|
||||
public String getEtag() {
|
||||
return Etag;
|
||||
}
|
||||
|
||||
public void setDateCreated(Date DateCreated) {
|
||||
this.DateCreated = DateCreated;
|
||||
}
|
||||
public Date getDateCreated() {
|
||||
return DateCreated;
|
||||
}
|
||||
|
||||
public void setCanDelete(boolean CanDelete) {
|
||||
this.CanDelete = CanDelete;
|
||||
}
|
||||
public boolean getCanDelete() {
|
||||
return CanDelete;
|
||||
}
|
||||
|
||||
public void setCanDownload(boolean CanDownload) {
|
||||
this.CanDownload = CanDownload;
|
||||
}
|
||||
public boolean getCanDownload() {
|
||||
return CanDownload;
|
||||
}
|
||||
|
||||
public void setHasSubtitles(boolean HasSubtitles) {
|
||||
this.HasSubtitles = HasSubtitles;
|
||||
}
|
||||
public boolean getHasSubtitles() {
|
||||
return HasSubtitles;
|
||||
}
|
||||
|
||||
public void setContainer(String Container) {
|
||||
this.Container = Container;
|
||||
}
|
||||
public String getContainer() {
|
||||
return Container;
|
||||
}
|
||||
|
||||
public void setSortName(String SortName) {
|
||||
this.SortName = SortName;
|
||||
}
|
||||
public String getSortName() {
|
||||
return SortName;
|
||||
}
|
||||
|
||||
public void setPremiereDate(String PremiereDate) {
|
||||
this.PremiereDate = PremiereDate;
|
||||
}
|
||||
public String getPremiereDate() {
|
||||
return PremiereDate;
|
||||
}
|
||||
|
||||
public void setExternalUrls(List<ExternalUrls> ExternalUrls) {
|
||||
this.ExternalUrls = ExternalUrls;
|
||||
}
|
||||
public List<ExternalUrls> getExternalUrls() {
|
||||
return ExternalUrls;
|
||||
}
|
||||
|
||||
public void setMediaSources(List<MediaSources> MediaSources) {
|
||||
this.MediaSources = MediaSources;
|
||||
}
|
||||
public List<MediaSources> getMediaSources() {
|
||||
return MediaSources;
|
||||
}
|
||||
|
||||
public void setProductionLocations(List<String> ProductionLocations) {
|
||||
this.ProductionLocations = ProductionLocations;
|
||||
}
|
||||
public List<String> getProductionLocations() {
|
||||
return ProductionLocations;
|
||||
}
|
||||
|
||||
public void setPath(String Path) {
|
||||
this.Path = Path;
|
||||
}
|
||||
public String getPath() {
|
||||
return Path;
|
||||
}
|
||||
|
||||
public void setEnableMediaSourceDisplay(boolean EnableMediaSourceDisplay) {
|
||||
this.EnableMediaSourceDisplay = EnableMediaSourceDisplay;
|
||||
}
|
||||
public boolean getEnableMediaSourceDisplay() {
|
||||
return EnableMediaSourceDisplay;
|
||||
}
|
||||
|
||||
public void setOfficialRating(String OfficialRating) {
|
||||
this.OfficialRating = OfficialRating;
|
||||
}
|
||||
public String getOfficialRating() {
|
||||
return OfficialRating;
|
||||
}
|
||||
|
||||
public void setChannelId(String ChannelId) {
|
||||
this.ChannelId = ChannelId;
|
||||
}
|
||||
public String getChannelId() {
|
||||
return ChannelId;
|
||||
}
|
||||
|
||||
public void setOverview(String Overview) {
|
||||
this.Overview = Overview;
|
||||
}
|
||||
public String getOverview() {
|
||||
return Overview;
|
||||
}
|
||||
|
||||
public void setTaglines(List<String> Taglines) {
|
||||
this.Taglines = Taglines;
|
||||
}
|
||||
public List<String> getTaglines() {
|
||||
return Taglines;
|
||||
}
|
||||
|
||||
public void setGenres(List<String> Genres) {
|
||||
this.Genres = Genres;
|
||||
}
|
||||
public List<String> getGenres() {
|
||||
return Genres;
|
||||
}
|
||||
|
||||
public void setCommunityRating(String CommunityRating) {
|
||||
this.CommunityRating = CommunityRating;
|
||||
}
|
||||
public String getCommunityRating() {
|
||||
return CommunityRating;
|
||||
}
|
||||
|
||||
public void setRunTimeTicks(long RunTimeTicks) {
|
||||
this.RunTimeTicks = RunTimeTicks;
|
||||
}
|
||||
public long getRunTimeTicks() {
|
||||
return RunTimeTicks;
|
||||
}
|
||||
|
||||
public void setPlayAccess(String PlayAccess) {
|
||||
this.PlayAccess = PlayAccess;
|
||||
}
|
||||
public String getPlayAccess() {
|
||||
return PlayAccess;
|
||||
}
|
||||
|
||||
public void setProductionYear(String ProductionYear) {
|
||||
this.ProductionYear = ProductionYear;
|
||||
}
|
||||
public String getProductionYear() {
|
||||
return ProductionYear;
|
||||
}
|
||||
|
||||
// public void setRemoteTrailers(List<String> RemoteTrailers) {
|
||||
// this.RemoteTrailers = RemoteTrailers;
|
||||
// }
|
||||
// public List<String> getRemoteTrailers() {
|
||||
// return RemoteTrailers;
|
||||
// }
|
||||
|
||||
public void setProviderIds(ProviderIds ProviderIds) {
|
||||
this.ProviderIds = ProviderIds;
|
||||
}
|
||||
public ProviderIds getProviderIds() {
|
||||
return ProviderIds;
|
||||
}
|
||||
|
||||
public void setIsHD(boolean IsHD) {
|
||||
this.IsHD = IsHD;
|
||||
}
|
||||
public boolean getIsHD() {
|
||||
return IsHD;
|
||||
}
|
||||
|
||||
public void setIsFolder(boolean IsFolder) {
|
||||
this.IsFolder = IsFolder;
|
||||
}
|
||||
public boolean getIsFolder() {
|
||||
return IsFolder;
|
||||
}
|
||||
|
||||
public void setParentId(String ParentId) {
|
||||
this.ParentId = ParentId;
|
||||
}
|
||||
public String getParentId() {
|
||||
return ParentId;
|
||||
}
|
||||
|
||||
public void setType(String Type) {
|
||||
this.Type = Type;
|
||||
}
|
||||
public String getType() {
|
||||
return Type;
|
||||
}
|
||||
|
||||
public void setPeople(List<People> People) {
|
||||
this.People = People;
|
||||
}
|
||||
public List<People> getPeople() {
|
||||
return People;
|
||||
}
|
||||
|
||||
public void setStudios(List<Studios> Studios) {
|
||||
this.Studios = Studios;
|
||||
}
|
||||
public List<Studios> getStudios() {
|
||||
return Studios;
|
||||
}
|
||||
|
||||
public void setGenreItems(List<GenreItems> GenreItems) {
|
||||
this.GenreItems = GenreItems;
|
||||
}
|
||||
public List<GenreItems> getGenreItems() {
|
||||
return GenreItems;
|
||||
}
|
||||
|
||||
public void setLocalTrailerCount(int LocalTrailerCount) {
|
||||
this.LocalTrailerCount = LocalTrailerCount;
|
||||
}
|
||||
public int getLocalTrailerCount() {
|
||||
return LocalTrailerCount;
|
||||
}
|
||||
|
||||
public void setUserData(UserData UserData) {
|
||||
this.UserData = UserData;
|
||||
}
|
||||
public UserData getUserData() {
|
||||
return UserData;
|
||||
}
|
||||
|
||||
public void setSpecialFeatureCount(int SpecialFeatureCount) {
|
||||
this.SpecialFeatureCount = SpecialFeatureCount;
|
||||
}
|
||||
public int getSpecialFeatureCount() {
|
||||
return SpecialFeatureCount;
|
||||
}
|
||||
|
||||
public void setDisplayPreferencesId(String DisplayPreferencesId) {
|
||||
this.DisplayPreferencesId = DisplayPreferencesId;
|
||||
}
|
||||
public String getDisplayPreferencesId() {
|
||||
return DisplayPreferencesId;
|
||||
}
|
||||
|
||||
public void setTags(List<String> Tags) {
|
||||
this.Tags = Tags;
|
||||
}
|
||||
public List<String> getTags() {
|
||||
return Tags;
|
||||
}
|
||||
|
||||
public void setPrimaryImageAspectRatio(double PrimaryImageAspectRatio) {
|
||||
this.PrimaryImageAspectRatio = PrimaryImageAspectRatio;
|
||||
}
|
||||
public double getPrimaryImageAspectRatio() {
|
||||
return PrimaryImageAspectRatio;
|
||||
}
|
||||
|
||||
public void setMediaStreams(List<MediaStreams> MediaStreams) {
|
||||
this.MediaStreams = MediaStreams;
|
||||
}
|
||||
public List<MediaStreams> getMediaStreams() {
|
||||
return MediaStreams;
|
||||
}
|
||||
|
||||
public void setVideoType(String VideoType) {
|
||||
this.VideoType = VideoType;
|
||||
}
|
||||
public String getVideoType() {
|
||||
return VideoType;
|
||||
}
|
||||
|
||||
public void setImageTags(ImageTags ImageTags) {
|
||||
this.ImageTags = ImageTags;
|
||||
}
|
||||
public ImageTags getImageTags() {
|
||||
return ImageTags;
|
||||
}
|
||||
|
||||
public void setBackdropImageTags(List<String> BackdropImageTags) {
|
||||
this.BackdropImageTags = BackdropImageTags;
|
||||
}
|
||||
public List<String> getBackdropImageTags() {
|
||||
return BackdropImageTags;
|
||||
}
|
||||
|
||||
public void setChapters(List<Chapters> Chapters) {
|
||||
this.Chapters = Chapters;
|
||||
}
|
||||
public List<Chapters> getChapters() {
|
||||
return Chapters;
|
||||
}
|
||||
|
||||
public void setLocationType(String LocationType) {
|
||||
this.LocationType = LocationType;
|
||||
}
|
||||
public String getLocationType() {
|
||||
return LocationType;
|
||||
}
|
||||
|
||||
public void setMediaType(String MediaType) {
|
||||
this.MediaType = MediaType;
|
||||
}
|
||||
public String getMediaType() {
|
||||
return MediaType;
|
||||
}
|
||||
|
||||
public void setLockedFields(List<String> LockedFields) {
|
||||
this.LockedFields = LockedFields;
|
||||
}
|
||||
public List<String> getLockedFields() {
|
||||
return LockedFields;
|
||||
}
|
||||
|
||||
public void setLockData(boolean LockData) {
|
||||
this.LockData = LockData;
|
||||
}
|
||||
public boolean getLockData() {
|
||||
return LockData;
|
||||
}
|
||||
|
||||
public void setWidth(int Width) {
|
||||
this.Width = Width;
|
||||
}
|
||||
public int getWidth() {
|
||||
return Width;
|
||||
}
|
||||
|
||||
public void setHeight(int Height) {
|
||||
this.Height = Height;
|
||||
}
|
||||
public int getHeight() {
|
||||
return Height;
|
||||
}
|
||||
|
||||
}
|
37
app/src/main/java/org/sifacai/vlcjellyfin/Bean/Items.java
Normal file
@ -0,0 +1,37 @@
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Items {
|
||||
private List<Item> Items;
|
||||
private int StartIndex;
|
||||
private int TotalRecordCount;
|
||||
|
||||
public List<Item> getItems() {
|
||||
return Items;
|
||||
}
|
||||
|
||||
public void setItems(List<Item> items) {
|
||||
Items = items;
|
||||
}
|
||||
|
||||
public void AddItems(List<Item> items) {
|
||||
Items.addAll(items);
|
||||
}
|
||||
|
||||
public int getStartIndex() {
|
||||
return StartIndex;
|
||||
}
|
||||
|
||||
public void setStartIndex(int startIndex) {
|
||||
StartIndex = startIndex;
|
||||
}
|
||||
|
||||
public int getTotalRecordCount() {
|
||||
return TotalRecordCount;
|
||||
}
|
||||
|
||||
public void setTotalRecordCount(int totalRecordCount) {
|
||||
TotalRecordCount = totalRecordCount;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
|
||||
public class MediaAttachments {
|
||||
private String Codec;
|
||||
private String CodecTag;
|
||||
private int Index;
|
||||
private String FileName;
|
||||
private String MimeType;
|
||||
|
||||
public String getCodec() {
|
||||
return Codec;
|
||||
}
|
||||
|
||||
public void setCodec(String codec) {
|
||||
Codec = codec;
|
||||
}
|
||||
|
||||
public String getCodecTag() {
|
||||
return CodecTag;
|
||||
}
|
||||
|
||||
public void setCodecTag(String codecTag) {
|
||||
CodecTag = codecTag;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return Index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return FileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
FileName = fileName;
|
||||
}
|
||||
|
||||
public String getMimeType() {
|
||||
return MimeType;
|
||||
}
|
||||
|
||||
public void setMimeType(String mimeType) {
|
||||
MimeType = mimeType;
|
||||
}
|
||||
}
|
247
app/src/main/java/org/sifacai/vlcjellyfin/Bean/MediaSources.java
Normal file
@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Copyright 2022 json.cn
|
||||
*/
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Auto-generated: 2022-09-01 14:6:30
|
||||
*
|
||||
* @author json.cn (i@json.cn)
|
||||
* @website http://www.json.cn/java2pojo/
|
||||
*/
|
||||
public class MediaSources {
|
||||
|
||||
private String Protocol;
|
||||
private String Id;
|
||||
private String Path;
|
||||
private String Type;
|
||||
private String Container;
|
||||
private long Size;
|
||||
private String Name;
|
||||
private boolean IsRemote;
|
||||
private String ETag;
|
||||
private long RunTimeTicks;
|
||||
private boolean ReadAtNativeFramerate;
|
||||
private boolean IgnoreDts;
|
||||
private boolean IgnoreIndex;
|
||||
private boolean GenPtsInput;
|
||||
private boolean SupportsTranscoding;
|
||||
private boolean SupportsDirectStream;
|
||||
private boolean SupportsDirectPlay;
|
||||
private boolean IsInfiniteStream;
|
||||
private boolean RequiresOpening;
|
||||
private boolean RequiresClosing;
|
||||
private boolean RequiresLooping;
|
||||
private boolean SupportsProbing;
|
||||
private String VideoType;
|
||||
private List<MediaStreams> MediaStreams;
|
||||
private List<MediaAttachments> MediaAttachments;
|
||||
private List<String> Formats;
|
||||
private long Bitrate;
|
||||
private int DefaultAudioStreamIndex;
|
||||
private int DefaultSubtitleStreamIndex;
|
||||
public void setProtocol(String Protocol) {
|
||||
this.Protocol = Protocol;
|
||||
}
|
||||
public String getProtocol() {
|
||||
return Protocol;
|
||||
}
|
||||
|
||||
public void setId(String Id) {
|
||||
this.Id = Id;
|
||||
}
|
||||
public String getId() {
|
||||
return Id;
|
||||
}
|
||||
|
||||
public void setPath(String Path) {
|
||||
this.Path = Path;
|
||||
}
|
||||
public String getPath() {
|
||||
return Path;
|
||||
}
|
||||
|
||||
public void setType(String Type) {
|
||||
this.Type = Type;
|
||||
}
|
||||
public String getType() {
|
||||
return Type;
|
||||
}
|
||||
|
||||
public void setContainer(String Container) {
|
||||
this.Container = Container;
|
||||
}
|
||||
public String getContainer() {
|
||||
return Container;
|
||||
}
|
||||
|
||||
public void setSize(long Size) {
|
||||
this.Size = Size;
|
||||
}
|
||||
public long getSize() {
|
||||
return Size;
|
||||
}
|
||||
|
||||
public void setName(String Name) {
|
||||
this.Name = Name;
|
||||
}
|
||||
public String getName() {
|
||||
return Name;
|
||||
}
|
||||
|
||||
public void setIsRemote(boolean IsRemote) {
|
||||
this.IsRemote = IsRemote;
|
||||
}
|
||||
public boolean getIsRemote() {
|
||||
return IsRemote;
|
||||
}
|
||||
|
||||
public void setETag(String ETag) {
|
||||
this.ETag = ETag;
|
||||
}
|
||||
public String getETag() {
|
||||
return ETag;
|
||||
}
|
||||
|
||||
public void setRunTimeTicks(long RunTimeTicks) {
|
||||
this.RunTimeTicks = RunTimeTicks;
|
||||
}
|
||||
public long getRunTimeTicks() {
|
||||
return RunTimeTicks;
|
||||
}
|
||||
|
||||
public void setReadAtNativeFramerate(boolean ReadAtNativeFramerate) {
|
||||
this.ReadAtNativeFramerate = ReadAtNativeFramerate;
|
||||
}
|
||||
public boolean getReadAtNativeFramerate() {
|
||||
return ReadAtNativeFramerate;
|
||||
}
|
||||
|
||||
public void setIgnoreDts(boolean IgnoreDts) {
|
||||
this.IgnoreDts = IgnoreDts;
|
||||
}
|
||||
public boolean getIgnoreDts() {
|
||||
return IgnoreDts;
|
||||
}
|
||||
|
||||
public void setIgnoreIndex(boolean IgnoreIndex) {
|
||||
this.IgnoreIndex = IgnoreIndex;
|
||||
}
|
||||
public boolean getIgnoreIndex() {
|
||||
return IgnoreIndex;
|
||||
}
|
||||
|
||||
public void setGenPtsInput(boolean GenPtsInput) {
|
||||
this.GenPtsInput = GenPtsInput;
|
||||
}
|
||||
public boolean getGenPtsInput() {
|
||||
return GenPtsInput;
|
||||
}
|
||||
|
||||
public void setSupportsTranscoding(boolean SupportsTranscoding) {
|
||||
this.SupportsTranscoding = SupportsTranscoding;
|
||||
}
|
||||
public boolean getSupportsTranscoding() {
|
||||
return SupportsTranscoding;
|
||||
}
|
||||
|
||||
public void setSupportsDirectStream(boolean SupportsDirectStream) {
|
||||
this.SupportsDirectStream = SupportsDirectStream;
|
||||
}
|
||||
public boolean getSupportsDirectStream() {
|
||||
return SupportsDirectStream;
|
||||
}
|
||||
|
||||
public void setSupportsDirectPlay(boolean SupportsDirectPlay) {
|
||||
this.SupportsDirectPlay = SupportsDirectPlay;
|
||||
}
|
||||
public boolean getSupportsDirectPlay() {
|
||||
return SupportsDirectPlay;
|
||||
}
|
||||
|
||||
public void setIsInfiniteStream(boolean IsInfiniteStream) {
|
||||
this.IsInfiniteStream = IsInfiniteStream;
|
||||
}
|
||||
public boolean getIsInfiniteStream() {
|
||||
return IsInfiniteStream;
|
||||
}
|
||||
|
||||
public void setRequiresOpening(boolean RequiresOpening) {
|
||||
this.RequiresOpening = RequiresOpening;
|
||||
}
|
||||
public boolean getRequiresOpening() {
|
||||
return RequiresOpening;
|
||||
}
|
||||
|
||||
public void setRequiresClosing(boolean RequiresClosing) {
|
||||
this.RequiresClosing = RequiresClosing;
|
||||
}
|
||||
public boolean getRequiresClosing() {
|
||||
return RequiresClosing;
|
||||
}
|
||||
|
||||
public void setRequiresLooping(boolean RequiresLooping) {
|
||||
this.RequiresLooping = RequiresLooping;
|
||||
}
|
||||
public boolean getRequiresLooping() {
|
||||
return RequiresLooping;
|
||||
}
|
||||
|
||||
public void setSupportsProbing(boolean SupportsProbing) {
|
||||
this.SupportsProbing = SupportsProbing;
|
||||
}
|
||||
public boolean getSupportsProbing() {
|
||||
return SupportsProbing;
|
||||
}
|
||||
|
||||
public void setVideoType(String VideoType) {
|
||||
this.VideoType = VideoType;
|
||||
}
|
||||
public String getVideoType() {
|
||||
return VideoType;
|
||||
}
|
||||
|
||||
public void setMediaStreams(List<MediaStreams> MediaStreams) {
|
||||
this.MediaStreams = MediaStreams;
|
||||
}
|
||||
public List<MediaStreams> getMediaStreams() {
|
||||
return MediaStreams;
|
||||
}
|
||||
|
||||
public void setMediaAttachments(List<MediaAttachments> MediaAttachments) {
|
||||
this.MediaAttachments = MediaAttachments;
|
||||
}
|
||||
public List<MediaAttachments> getMediaAttachments() {
|
||||
return MediaAttachments;
|
||||
}
|
||||
|
||||
public void setFormats(List<String> Formats) {
|
||||
this.Formats = Formats;
|
||||
}
|
||||
public List<String> getFormats() {
|
||||
return Formats;
|
||||
}
|
||||
|
||||
public void setBitrate(long Bitrate) {
|
||||
this.Bitrate = Bitrate;
|
||||
}
|
||||
public long getBitrate() {
|
||||
return Bitrate;
|
||||
}
|
||||
|
||||
public void setDefaultAudioStreamIndex(int DefaultAudioStreamIndex) {
|
||||
this.DefaultAudioStreamIndex = DefaultAudioStreamIndex;
|
||||
}
|
||||
public int getDefaultAudioStreamIndex() {
|
||||
return DefaultAudioStreamIndex;
|
||||
}
|
||||
|
||||
public void setDefaultSubtitleStreamIndex(int DefaultSubtitleStreamIndex) {
|
||||
this.DefaultSubtitleStreamIndex = DefaultSubtitleStreamIndex;
|
||||
}
|
||||
public int getDefaultSubtitleStreamIndex() {
|
||||
return DefaultSubtitleStreamIndex;
|
||||
}
|
||||
|
||||
}
|
247
app/src/main/java/org/sifacai/vlcjellyfin/Bean/MediaStreams.java
Normal file
@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Copyright 2022 json.cn
|
||||
*/
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Auto-generated: 2022-09-01 14:6:30
|
||||
*
|
||||
* @author json.cn (i@json.cn)
|
||||
* @website http://www.json.cn/java2pojo/
|
||||
*/
|
||||
public class MediaStreams {
|
||||
|
||||
private String Codec;
|
||||
private String Language;
|
||||
private String ColorSpace;
|
||||
private String ColorTransfer;
|
||||
private String ColorPrimaries;
|
||||
private String TimeBase;
|
||||
private String Title;
|
||||
private String VideoRange;
|
||||
private String VideoRangeType;
|
||||
private String DisplayTitle;
|
||||
private boolean IsInterlaced;
|
||||
private long BitRate;
|
||||
private int BitDepth;
|
||||
private int RefFrames;
|
||||
private boolean IsDefault;
|
||||
private boolean IsForced;
|
||||
private int Height;
|
||||
private int Width;
|
||||
private double AverageFrameRate;
|
||||
private double RealFrameRate;
|
||||
private String Profile;
|
||||
private String Type;
|
||||
private String AspectRatio;
|
||||
private int Index;
|
||||
private boolean IsExternal;
|
||||
private boolean IsTextSubtitleStream;
|
||||
private boolean SupportsExternalStream;
|
||||
private String PixelFormat;
|
||||
private int Level;
|
||||
public void setCodec(String Codec) {
|
||||
this.Codec = Codec;
|
||||
}
|
||||
public String getCodec() {
|
||||
return Codec;
|
||||
}
|
||||
|
||||
public void setLanguage(String Language) {
|
||||
this.Language = Language;
|
||||
}
|
||||
public String getLanguage() {
|
||||
return Language;
|
||||
}
|
||||
|
||||
public void setColorSpace(String ColorSpace) {
|
||||
this.ColorSpace = ColorSpace;
|
||||
}
|
||||
public String getColorSpace() {
|
||||
return ColorSpace;
|
||||
}
|
||||
|
||||
public void setColorTransfer(String ColorTransfer) {
|
||||
this.ColorTransfer = ColorTransfer;
|
||||
}
|
||||
public String getColorTransfer() {
|
||||
return ColorTransfer;
|
||||
}
|
||||
|
||||
public void setColorPrimaries(String ColorPrimaries) {
|
||||
this.ColorPrimaries = ColorPrimaries;
|
||||
}
|
||||
public String getColorPrimaries() {
|
||||
return ColorPrimaries;
|
||||
}
|
||||
|
||||
public void setTimeBase(String TimeBase) {
|
||||
this.TimeBase = TimeBase;
|
||||
}
|
||||
public String getTimeBase() {
|
||||
return TimeBase;
|
||||
}
|
||||
|
||||
public void setTitle(String Title) {
|
||||
this.Title = Title;
|
||||
}
|
||||
public String getTitle() {
|
||||
return Title;
|
||||
}
|
||||
|
||||
public void setVideoRange(String VideoRange) {
|
||||
this.VideoRange = VideoRange;
|
||||
}
|
||||
public String getVideoRange() {
|
||||
return VideoRange;
|
||||
}
|
||||
|
||||
public void setVideoRangeType(String VideoRangeType) {
|
||||
this.VideoRangeType = VideoRangeType;
|
||||
}
|
||||
public String getVideoRangeType() {
|
||||
return VideoRangeType;
|
||||
}
|
||||
|
||||
public void setDisplayTitle(String DisplayTitle) {
|
||||
this.DisplayTitle = DisplayTitle;
|
||||
}
|
||||
public String getDisplayTitle() {
|
||||
return DisplayTitle;
|
||||
}
|
||||
|
||||
public void setIsInterlaced(boolean IsInterlaced) {
|
||||
this.IsInterlaced = IsInterlaced;
|
||||
}
|
||||
public boolean getIsInterlaced() {
|
||||
return IsInterlaced;
|
||||
}
|
||||
|
||||
public void setBitRate(long BitRate) {
|
||||
this.BitRate = BitRate;
|
||||
}
|
||||
public long getBitRate() {
|
||||
return BitRate;
|
||||
}
|
||||
|
||||
public void setBitDepth(int BitDepth) {
|
||||
this.BitDepth = BitDepth;
|
||||
}
|
||||
public int getBitDepth() {
|
||||
return BitDepth;
|
||||
}
|
||||
|
||||
public void setRefFrames(int RefFrames) {
|
||||
this.RefFrames = RefFrames;
|
||||
}
|
||||
public int getRefFrames() {
|
||||
return RefFrames;
|
||||
}
|
||||
|
||||
public void setIsDefault(boolean IsDefault) {
|
||||
this.IsDefault = IsDefault;
|
||||
}
|
||||
public boolean getIsDefault() {
|
||||
return IsDefault;
|
||||
}
|
||||
|
||||
public void setIsForced(boolean IsForced) {
|
||||
this.IsForced = IsForced;
|
||||
}
|
||||
public boolean getIsForced() {
|
||||
return IsForced;
|
||||
}
|
||||
|
||||
public void setHeight(int Height) {
|
||||
this.Height = Height;
|
||||
}
|
||||
public int getHeight() {
|
||||
return Height;
|
||||
}
|
||||
|
||||
public void setWidth(int Width) {
|
||||
this.Width = Width;
|
||||
}
|
||||
public int getWidth() {
|
||||
return Width;
|
||||
}
|
||||
|
||||
public void setAverageFrameRate(double AverageFrameRate) {
|
||||
this.AverageFrameRate = AverageFrameRate;
|
||||
}
|
||||
public double getAverageFrameRate() {
|
||||
return AverageFrameRate;
|
||||
}
|
||||
|
||||
public void setRealFrameRate(double RealFrameRate) {
|
||||
this.RealFrameRate = RealFrameRate;
|
||||
}
|
||||
public double getRealFrameRate() {
|
||||
return RealFrameRate;
|
||||
}
|
||||
|
||||
public void setProfile(String Profile) {
|
||||
this.Profile = Profile;
|
||||
}
|
||||
public String getProfile() {
|
||||
return Profile;
|
||||
}
|
||||
|
||||
public void setType(String Type) {
|
||||
this.Type = Type;
|
||||
}
|
||||
public String getType() {
|
||||
return Type;
|
||||
}
|
||||
|
||||
public void setAspectRatio(String AspectRatio) {
|
||||
this.AspectRatio = AspectRatio;
|
||||
}
|
||||
public String getAspectRatio() {
|
||||
return AspectRatio;
|
||||
}
|
||||
|
||||
public void setIndex(int Index) {
|
||||
this.Index = Index;
|
||||
}
|
||||
public int getIndex() {
|
||||
return Index;
|
||||
}
|
||||
|
||||
public void setIsExternal(boolean IsExternal) {
|
||||
this.IsExternal = IsExternal;
|
||||
}
|
||||
public boolean getIsExternal() {
|
||||
return IsExternal;
|
||||
}
|
||||
|
||||
public void setIsTextSubtitleStream(boolean IsTextSubtitleStream) {
|
||||
this.IsTextSubtitleStream = IsTextSubtitleStream;
|
||||
}
|
||||
public boolean getIsTextSubtitleStream() {
|
||||
return IsTextSubtitleStream;
|
||||
}
|
||||
|
||||
public void setSupportsExternalStream(boolean SupportsExternalStream) {
|
||||
this.SupportsExternalStream = SupportsExternalStream;
|
||||
}
|
||||
public boolean getSupportsExternalStream() {
|
||||
return SupportsExternalStream;
|
||||
}
|
||||
|
||||
public void setPixelFormat(String PixelFormat) {
|
||||
this.PixelFormat = PixelFormat;
|
||||
}
|
||||
public String getPixelFormat() {
|
||||
return PixelFormat;
|
||||
}
|
||||
|
||||
public void setLevel(int Level) {
|
||||
this.Level = Level;
|
||||
}
|
||||
public int getLevel() {
|
||||
return Level;
|
||||
}
|
||||
|
||||
}
|
54
app/src/main/java/org/sifacai/vlcjellyfin/Bean/People.java
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright 2022 json.cn
|
||||
*/
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
|
||||
/**
|
||||
* Auto-generated: 2022-09-01 14:6:30
|
||||
*
|
||||
* @author json.cn (i@json.cn)
|
||||
* @website http://www.json.cn/java2pojo/
|
||||
*/
|
||||
public class People {
|
||||
|
||||
private String Name;
|
||||
private String Id;
|
||||
private String Role;
|
||||
private String Type;
|
||||
private String PrimaryImageTag;
|
||||
public void setName(String Name) {
|
||||
this.Name = Name;
|
||||
}
|
||||
public String getName() {
|
||||
return Name;
|
||||
}
|
||||
|
||||
public void setId(String Id) {
|
||||
this.Id = Id;
|
||||
}
|
||||
public String getId() {
|
||||
return Id;
|
||||
}
|
||||
|
||||
public void setRole(String Role) {
|
||||
this.Role = Role;
|
||||
}
|
||||
public String getRole() {
|
||||
return Role;
|
||||
}
|
||||
|
||||
public void setType(String Type) {
|
||||
this.Type = Type;
|
||||
}
|
||||
public String getType() {
|
||||
return Type;
|
||||
}
|
||||
|
||||
public void setPrimaryImageTag(String PrimaryImageTag) {
|
||||
this.PrimaryImageTag = PrimaryImageTag;
|
||||
}
|
||||
public String getPrimaryImageTag() {
|
||||
return PrimaryImageTag;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright 2022 json.cn
|
||||
*/
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
|
||||
/**
|
||||
* Auto-generated: 2022-09-01 14:6:30
|
||||
*
|
||||
* @author json.cn (i@json.cn)
|
||||
* @website http://www.json.cn/java2pojo/
|
||||
*/
|
||||
public class ProviderIds {
|
||||
|
||||
private String Tmdb;
|
||||
private String Imdb;
|
||||
public void setTmdb(String Tmdb) {
|
||||
this.Tmdb = Tmdb;
|
||||
}
|
||||
public String getTmdb() {
|
||||
return Tmdb;
|
||||
}
|
||||
|
||||
public void setImdb(String Imdb) {
|
||||
this.Imdb = Imdb;
|
||||
}
|
||||
public String getImdb() {
|
||||
return Imdb;
|
||||
}
|
||||
|
||||
}
|
30
app/src/main/java/org/sifacai/vlcjellyfin/Bean/Studios.java
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright 2022 json.cn
|
||||
*/
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
|
||||
/**
|
||||
* Auto-generated: 2022-09-01 14:6:30
|
||||
*
|
||||
* @author json.cn (i@json.cn)
|
||||
* @website http://www.json.cn/java2pojo/
|
||||
*/
|
||||
public class Studios {
|
||||
|
||||
private String Name;
|
||||
private String Id;
|
||||
public void setName(String Name) {
|
||||
this.Name = Name;
|
||||
}
|
||||
public String getName() {
|
||||
return Name;
|
||||
}
|
||||
|
||||
public void setId(String Id) {
|
||||
this.Id = Id;
|
||||
}
|
||||
public String getId() {
|
||||
return Id;
|
||||
}
|
||||
|
||||
}
|
85
app/src/main/java/org/sifacai/vlcjellyfin/Bean/UserData.java
Normal file
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright 2022 json.cn
|
||||
*/
|
||||
package org.sifacai.vlcjellyfin.Bean;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Auto-generated: 2022-09-01 14:6:30
|
||||
*
|
||||
* @author json.cn (i@json.cn)
|
||||
* @website http://www.json.cn/java2pojo/
|
||||
*/
|
||||
public class UserData {
|
||||
|
||||
private long PlaybackPositionTicks;
|
||||
private int PlayCount;
|
||||
private boolean IsFavorite;
|
||||
private Date LastPlayedDate;
|
||||
private boolean Played;
|
||||
private String Key;
|
||||
private double PlayedPercentage;
|
||||
|
||||
public boolean isFavorite() {
|
||||
return IsFavorite;
|
||||
}
|
||||
|
||||
public void setFavorite(boolean favorite) {
|
||||
IsFavorite = favorite;
|
||||
}
|
||||
|
||||
public boolean isPlayed() {
|
||||
return Played;
|
||||
}
|
||||
|
||||
public double getPlayedPercentage() {
|
||||
return PlayedPercentage;
|
||||
}
|
||||
|
||||
public void setPlayedPercentage(double playedPercentage) {
|
||||
PlayedPercentage = playedPercentage;
|
||||
}
|
||||
|
||||
public void setPlaybackPositionTicks(long PlaybackPositionTicks) {
|
||||
this.PlaybackPositionTicks = PlaybackPositionTicks;
|
||||
}
|
||||
public long getPlaybackPositionTicks() {
|
||||
return PlaybackPositionTicks;
|
||||
}
|
||||
|
||||
public void setPlayCount(int PlayCount) {
|
||||
this.PlayCount = PlayCount;
|
||||
}
|
||||
public int getPlayCount() {
|
||||
return PlayCount;
|
||||
}
|
||||
|
||||
public void setIsFavorite(boolean IsFavorite) {
|
||||
this.IsFavorite = IsFavorite;
|
||||
}
|
||||
public boolean getIsFavorite() {
|
||||
return IsFavorite;
|
||||
}
|
||||
|
||||
public void setLastPlayedDate(Date LastPlayedDate) {
|
||||
this.LastPlayedDate = LastPlayedDate;
|
||||
}
|
||||
public Date getLastPlayedDate() {
|
||||
return LastPlayedDate;
|
||||
}
|
||||
|
||||
public void setPlayed(boolean Played) {
|
||||
this.Played = Played;
|
||||
}
|
||||
public boolean getPlayed() {
|
||||
return Played;
|
||||
}
|
||||
|
||||
public void setKey(String Key) {
|
||||
this.Key = Key;
|
||||
}
|
||||
public String getKey() {
|
||||
return Key;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.ActionMode;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.owen.tvrecyclerview.widget.TvRecyclerView;
|
||||
import com.owen.tvrecyclerview.widget.V7GridLayoutManager;
|
||||
|
||||
import org.sifacai.vlcjellyfin.Bean.Item;
|
||||
import org.sifacai.vlcjellyfin.Bean.Items;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CollectionActivity extends BaseActivity {
|
||||
private String TAG = "CollectionActivity";
|
||||
private TvRecyclerView mGridContiner = null;
|
||||
private TextView tvTitleTip = null;
|
||||
private String ItemId = "";
|
||||
private int currentPage = 1; //当前页码
|
||||
private int countPage = 1; //总页数
|
||||
private int limit = 60; //每页条目
|
||||
private int totalCount = 0; //总条目数
|
||||
private String Type = "";
|
||||
private Item currObj = null;
|
||||
private List<Item> currItems = null;
|
||||
private JAdapter currAdapter = null;
|
||||
|
||||
private TextView sortMenuBtn;
|
||||
|
||||
private PopupMenu SortByMenu;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_collection);
|
||||
|
||||
if (JfClient.UserId.equals("") || JfClient.AccessToken.equals("")) {
|
||||
finish();
|
||||
}
|
||||
|
||||
mGridContiner = findViewById(R.id.mGridView);
|
||||
tvTitleTip = findViewById(R.id.actionBar_titleTip);
|
||||
V7GridLayoutManager v7GridLayoutManager = new V7GridLayoutManager(this,6);
|
||||
mGridContiner.setLayoutManager(v7GridLayoutManager);
|
||||
mGridContiner.setItemAnimator(null); //防崩溃
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
Intent intent = getIntent();
|
||||
ItemId = intent.getStringExtra("itemId");
|
||||
if(ItemId.equals("")){
|
||||
finish();
|
||||
}else{
|
||||
currItems = new ArrayList<>();
|
||||
currAdapter = getJAdapter(currItems);
|
||||
mGridContiner.setAdapter(currAdapter);
|
||||
|
||||
initData();
|
||||
|
||||
initSortByMenu();
|
||||
}
|
||||
}
|
||||
|
||||
public void initData() {
|
||||
showLoadingDialog("加载中…………");
|
||||
JfClient.GetItemInfo(ItemId,new JfClient.JJCallBack(){
|
||||
@Override
|
||||
public void onSuccess(Item Collection) {
|
||||
currObj = Collection;
|
||||
Type = Collection.getType();
|
||||
fillItems();
|
||||
setLoadMore();
|
||||
}
|
||||
},null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载条目数据
|
||||
*/
|
||||
private void fillItems(){
|
||||
JfClient.GetCollection(ItemId,Type,
|
||||
JfClient.config.getSortBy(),
|
||||
JfClient.config.getSortOrder(),
|
||||
limit,currentPage,
|
||||
new JfClient.JJCallBack(){
|
||||
@Override
|
||||
public void onSuccess(Items items) {
|
||||
totalCount = items.getTotalRecordCount();
|
||||
countPage = (int) Math.ceil((double) totalCount / limit);
|
||||
List<Item> Items = items.getItems();
|
||||
dismissLoadingDialog();
|
||||
currAdapter.addItems(Items);
|
||||
setTitleTip();
|
||||
mGridContiner.finishLoadMore();
|
||||
}
|
||||
},null);
|
||||
}
|
||||
|
||||
private JAdapter getJAdapter(List<Item> items){
|
||||
JAdapter jAdapter = new JAdapter(items,false);
|
||||
jAdapter.setOnItemClickListener(new JAdapter.OnItemClickListener() {
|
||||
@Override
|
||||
public void onClick(Item item) {
|
||||
String type = item.getType();
|
||||
String itemId = item.getId();
|
||||
Intent intent = null;
|
||||
if(type.equals("Folder") || type.equals("CollectionFolder")){
|
||||
intent = new Intent(mAA,CollectionActivity.class);
|
||||
}else{
|
||||
intent = new Intent(mAA,DetailActivity.class);
|
||||
}
|
||||
intent.putExtra("itemId",itemId);
|
||||
mAA.startActivity(intent);
|
||||
}
|
||||
});
|
||||
return jAdapter;
|
||||
}
|
||||
|
||||
private void setLoadMore(){
|
||||
mGridContiner.setOnLoadMoreListener(new TvRecyclerView.OnLoadMoreListener() {
|
||||
@Override
|
||||
public void onLoadMore() {
|
||||
if(currentPage < countPage){
|
||||
currentPage += 1;
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fillItems();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setTitleTip(){
|
||||
String tip = "共 "+ totalCount +" ," + countPage + " 页,已加载" + currentPage + "页";
|
||||
tvTitleTip.setText(tip);
|
||||
}
|
||||
|
||||
private void initSortByMenu(){
|
||||
sortMenuBtn = findViewById(R.id.actionBar_sortBtn);
|
||||
sortMenuBtn.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
int i = 0;
|
||||
for(Config.SortByType sbt : Config.SortByType.values()){
|
||||
if(sbt.value.equals(JfClient.config.getSortBy())){
|
||||
i = sbt.ordinal();
|
||||
}
|
||||
}
|
||||
SortByMenu.show();
|
||||
}
|
||||
});
|
||||
|
||||
sortMenuBtn.setVisibility(View.VISIBLE);
|
||||
setSortMenuBtnText();
|
||||
SortByMenu = new PopupMenu(this,sortMenuBtn);
|
||||
Menu menu = SortByMenu.getMenu();
|
||||
Config.SortByType[] Ss = Config.SortByType.values();
|
||||
for (Config.SortByType sortby:Ss) {
|
||||
menu.add(0,sortby.ordinal(),sortby.ordinal(),sortby.name());
|
||||
}
|
||||
for (Config.SotrOrderType sot:Config.SotrOrderType.values()){
|
||||
menu.add(1,sot.ordinal() + Ss.length,sot.ordinal() + Ss.length,sot.name());
|
||||
}
|
||||
SortByMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
if(menuItem.getGroupId() == 0) {
|
||||
JfClient.config.setSortBy(Config.SortByType.valueOf(menuItem.getTitle().toString()).value);
|
||||
}else{
|
||||
JfClient.config.setSortOrder(Config.SotrOrderType.valueOf(menuItem.getTitle().toString()).value);
|
||||
}
|
||||
setSortMenuBtnText();
|
||||
currAdapter.clearItems();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
initData();
|
||||
}
|
||||
}).start();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setSortMenuBtnText(){
|
||||
String s = Config.SortByType.findName(JfClient.config.getSortBy());
|
||||
s += "-";
|
||||
s += Config.SotrOrderType.findName(JfClient.config.getSortOrder());
|
||||
sortMenuBtn.setText(s);
|
||||
}
|
||||
}
|
196
app/src/main/java/org/sifacai/vlcjellyfin/Config.java
Normal file
@ -0,0 +1,196 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public class Config {
|
||||
private Context context;
|
||||
private String JellyfinUrl = "";
|
||||
private String UserName = "";
|
||||
private String PassWord = "";
|
||||
private String SortBy = "DateCreated";
|
||||
private String SortOrder = "Descending";
|
||||
private boolean PlayStartInBegin; //是否从头开始播放 (接上次播放进度)
|
||||
private boolean HAACC; //硬解
|
||||
private boolean FORCE_HAACC; //强制硬解
|
||||
private boolean ExtensionPlayer; //调用外部播放器
|
||||
|
||||
public boolean isExtensionPlayer() {
|
||||
return ExtensionPlayer;
|
||||
}
|
||||
|
||||
public void setExtensionPlayer(boolean extensionPlayer) {
|
||||
ExtensionPlayer = extensionPlayer;
|
||||
saveConfigToSP("ExtensionPlayer",extensionPlayer);
|
||||
}
|
||||
|
||||
public boolean isPlayStartInBegin() {
|
||||
return PlayStartInBegin;
|
||||
}
|
||||
|
||||
public void setPlayStartInBegin(boolean playStartInBegin) {
|
||||
PlayStartInBegin = playStartInBegin;
|
||||
saveConfigToSP("PlayStartInBegin",playStartInBegin);
|
||||
}
|
||||
|
||||
public Config(Context context) {
|
||||
this.context = context;
|
||||
getConfigFromSP();
|
||||
}
|
||||
|
||||
public String getJellyfinUrl() {
|
||||
return JellyfinUrl;
|
||||
}
|
||||
|
||||
public void setJellyfinUrl(String jellyfinUrl) {
|
||||
JellyfinUrl = jellyfinUrl;
|
||||
saveConfigToSP("url",jellyfinUrl);
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return UserName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName) {
|
||||
UserName = userName;
|
||||
saveConfigToSP("username",userName);
|
||||
}
|
||||
|
||||
public String getPassWord() {
|
||||
return PassWord;
|
||||
}
|
||||
|
||||
public void setPassWord(String passWord) {
|
||||
PassWord = passWord;
|
||||
saveConfigToSP("password",passWord);
|
||||
}
|
||||
|
||||
public String getSortBy() {
|
||||
return SortBy;
|
||||
}
|
||||
|
||||
public void setSortBy(String sortBy) {
|
||||
SortBy = sortBy;
|
||||
saveConfigToSP("sortby",sortBy);
|
||||
}
|
||||
|
||||
public String getSortOrder() {
|
||||
return SortOrder;
|
||||
}
|
||||
|
||||
public void setSortOrder(String sortOrder) {
|
||||
SortOrder = sortOrder;
|
||||
saveConfigToSP("sortorder",sortOrder);
|
||||
}
|
||||
|
||||
public boolean isHAACC() {
|
||||
return HAACC;
|
||||
}
|
||||
|
||||
public void setHAACC(boolean HAACC) {
|
||||
this.HAACC = HAACC;
|
||||
saveConfigToSP("HAACC",HAACC);
|
||||
}
|
||||
|
||||
public boolean isFORCE_HAACC() {
|
||||
return FORCE_HAACC;
|
||||
}
|
||||
|
||||
public void setFORCE_HAACC(boolean FORCE_HAACC) {
|
||||
this.FORCE_HAACC = FORCE_HAACC;
|
||||
saveConfigToSP("FORCE_HAACC",FORCE_HAACC);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取配置
|
||||
*/
|
||||
public void getConfigFromSP() {
|
||||
SharedPreferences sp = context.getSharedPreferences("Jellyfin", context.MODE_PRIVATE);
|
||||
this.JellyfinUrl = sp.getString("url", "http://");
|
||||
this.UserName = sp.getString("username", "");
|
||||
this.PassWord = sp.getString("password", "");
|
||||
this.SortBy = sp.getString("sortby","DateCreated");
|
||||
this.SortOrder = sp.getString("sortorder","Descending");
|
||||
this.HAACC = sp.getBoolean("HAACC",true);
|
||||
this.FORCE_HAACC = sp.getBoolean("FORCE_HAACC",false);
|
||||
this.PlayStartInBegin = sp.getBoolean("PlayStartInBegin",true);
|
||||
this.ExtensionPlayer = sp.getBoolean("ExtensionPlayer",false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存单项配置
|
||||
*/
|
||||
public void saveConfigToSP(String key, String value) {
|
||||
SharedPreferences sp = context.getSharedPreferences("Jellyfin", context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.putString(key, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存单项配置
|
||||
*/
|
||||
public void saveConfigToSP(String key, boolean value) {
|
||||
SharedPreferences sp = context.getSharedPreferences("Jellyfin", context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.putBoolean(key, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除
|
||||
*/
|
||||
public void clear() {
|
||||
SharedPreferences sp = context.getSharedPreferences("Jellyfin", context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sp.edit();
|
||||
editor.clear();
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public enum SortByType {
|
||||
评分("CommunityRating"),
|
||||
加入日期("DateCreated"),
|
||||
播放日期("DatePlayed"),
|
||||
家长分级("OfficialRating"),
|
||||
播放次数("PlayCount"),
|
||||
发行日期("PremiereDate"),
|
||||
播放时长("Runtime");
|
||||
|
||||
public String value;
|
||||
|
||||
SortByType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static String findName(String value){
|
||||
String name = "";
|
||||
for (SortByType sbt:SortByType.values()) {
|
||||
if(sbt.value.equals(value)){
|
||||
name = sbt.name();
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public enum SotrOrderType{
|
||||
升序("Ascending"),
|
||||
降序("Descending");
|
||||
|
||||
public String value;
|
||||
|
||||
SotrOrderType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static String findName(String value){
|
||||
String name = "";
|
||||
for (SotrOrderType sot:SotrOrderType.values()) {
|
||||
if(sot.value.equals(value)){
|
||||
name = sot.name();
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
372
app/src/main/java/org/sifacai/vlcjellyfin/DetailActivity.java
Normal file
@ -0,0 +1,372 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.UriMatcher;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.animation.BounceInterpolator;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.owen.tvrecyclerview.widget.V7GridLayoutManager;
|
||||
import com.owen.tvrecyclerview.widget.V7LinearLayoutManager;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import org.sifacai.vlcjellyfin.Bean.ImageTags;
|
||||
import org.sifacai.vlcjellyfin.Bean.Item;
|
||||
import org.sifacai.vlcjellyfin.Bean.Items;
|
||||
import org.sifacai.vlcjellyfin.Bean.MediaStreams;
|
||||
import org.sifacai.vlcjellyfin.Bean.People;
|
||||
import org.sifacai.vlcjellyfin.Bean.UserData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class DetailActivity extends BaseActivity implements JAdapter.OnItemClickListener {
|
||||
private String TAG = "详情:";
|
||||
private String ItemId;
|
||||
private ImageView tvCover;
|
||||
private TextView tvTitle;
|
||||
private TextView tvDetails;
|
||||
private ImageView tvPlay;
|
||||
private JRecyclerView mGridView;
|
||||
private JRecyclerView mPeopleGridView;
|
||||
private LinearLayout tvPeopleLayout;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_detail);
|
||||
getSupportActionBar().hide();
|
||||
|
||||
if (JfClient.UserId.equals("") || JfClient.AccessToken.equals("")) {
|
||||
finish();
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
tvCover = findViewById(R.id.tvCover);
|
||||
tvTitle = findViewById(R.id.tvTitle);
|
||||
tvDetails = findViewById(R.id.tvDetails);
|
||||
tvPlay = findViewById(R.id.tvPlay);
|
||||
mGridView = findViewById(R.id.mGridView);
|
||||
tvPeopleLayout = findViewById(R.id.tvPersonLayout);
|
||||
mPeopleGridView = findViewById(R.id.mPersonGridView);
|
||||
|
||||
Intent intent = getIntent();
|
||||
ItemId = intent.getStringExtra("itemId");
|
||||
if (ItemId.equals("")) {
|
||||
finish();
|
||||
} else {
|
||||
showLoadingDialog("加载中……");
|
||||
initData(ItemId);
|
||||
}
|
||||
}
|
||||
|
||||
private void initData(String itemId) {
|
||||
JfClient.GetItemInfo(itemId, new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(Item item) {
|
||||
fillDetails(item);
|
||||
}
|
||||
}, new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onError(String str) {
|
||||
errcb.onError(str);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fillDetails(Item details) {
|
||||
String Id = details.getId();
|
||||
String Name = details.getName();
|
||||
String imgurl = JfClient.GetImgUrl(Id, details.getImageTags() == null ? "" : details.getImageTags().getPrimary());
|
||||
Picasso.get()
|
||||
.load(imgurl)
|
||||
.placeholder(R.drawable.img_loading_placeholder)
|
||||
.error(R.drawable.img_loading_placeholder)
|
||||
.into(tvCover);
|
||||
|
||||
String Genres = String.join(",", details.getGenres());
|
||||
|
||||
tvTitle.setText(Name);
|
||||
tvDetails.append(details.getProductionYear() == null ? "" : "年份:" + details.getProductionYear() + " ");
|
||||
tvDetails.append(Genres.equals("") ? "" : "风格:" + Genres + "\n");
|
||||
tvDetails.append(details.getCommunityRating() == null ? "" : "评分:" + details.getCommunityRating() + " ");
|
||||
tvDetails.append(details.getOfficialRating() == null ? "" : "评级:" + details.getOfficialRating() + "\n");
|
||||
|
||||
if (details.getMediaStreams() != null) {
|
||||
String video = "";
|
||||
String audio = "";
|
||||
String subtitle = "";
|
||||
for (int i = 0; i < details.getMediaStreams().size(); i++) {
|
||||
MediaStreams ms = details.getMediaStreams().get(i);
|
||||
String mstype = ms.getType();
|
||||
if (mstype.equals("Video")) {
|
||||
video += ms.getDisplayTitle();
|
||||
} else if (mstype.equals("Audio")) {
|
||||
if (ms.getLanguage()!=null && !ms.getLanguage().equals("")) audio += ms.getLanguage() + "、";
|
||||
else audio += ms.getCodec() + ";";
|
||||
} else if (mstype.equals("Subtitle")) {
|
||||
if (ms.getLanguage() != null && !ms.getLanguage().equals("")) subtitle += ms.getLanguage() + "、";
|
||||
else subtitle += ms.getCodec() + ";";
|
||||
}
|
||||
}
|
||||
tvDetails.append(video.equals("") ? "" : "视频:" + video + "\n");
|
||||
tvDetails.append(audio.equals("") ? "" : "音频:" + audio + "\n");
|
||||
tvDetails.append(subtitle.equals("") ? "" : "字幕:" + subtitle + "\n");
|
||||
}
|
||||
String overview = details.getOverview() == null ? "" : details.getOverview();
|
||||
tvDetails.append("简介: " + Html.fromHtml(overview));
|
||||
|
||||
//填充列表
|
||||
String type = details.getType();
|
||||
if (type.equals("Series")) {
|
||||
fillSeason(ItemId);
|
||||
} else if (type.equals("Season")) {
|
||||
String SeriesName = details.getSeriesName() == null ? "" : details.getSeriesName() + "-";
|
||||
tvTitle.setText(SeriesName + details.getName());
|
||||
String SeriesId = details.getSeriesId();
|
||||
fillEpisodes(SeriesId, ItemId);
|
||||
} else if (type.equals("Episode")) {
|
||||
String SeriesName = details.getSeriesName() == null ? "" : details.getSeriesName() + "-";
|
||||
String SeasonName = details.getSeasonName() == null ? "" : details.getSeasonName();
|
||||
tvTitle.setText(SeriesName + SeasonName);
|
||||
String SeriesId = details.getSeriesId();
|
||||
String SeasonId = details.getSeasonId();
|
||||
fillEpisodes(SeriesId, SeasonId);
|
||||
} else if (type.equals("Movie")) {
|
||||
fillMovie(details);
|
||||
} else if (type.equals("Person")) {
|
||||
tvDetails.append("\n出生日期:" + Utils.UtcToLocal(details.getPremiereDate()) + "\n");
|
||||
if(null != details.getProductionLocations()) {
|
||||
tvDetails.append("出生地:" + String.join(",", details.getProductionLocations()));
|
||||
}
|
||||
fillItemsByPerson(Id);
|
||||
}
|
||||
|
||||
List<People> Peoples = details.getPeople();
|
||||
if (Peoples != null) {
|
||||
if (Peoples.size() > 0) {
|
||||
fillPeople(Peoples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fillMovie(Item item) {
|
||||
|
||||
item.setName("播放: " + item.getName());
|
||||
List<Item> plist = new ArrayList<>();
|
||||
plist.add(item);
|
||||
if (item.getPartCount() > 0) {
|
||||
JfClient.GetAddPart(item.getId(), new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(Items parts) {
|
||||
plist.addAll(parts.getItems());
|
||||
fillItems(plist);
|
||||
}
|
||||
}, null);
|
||||
} else {
|
||||
fillItems(plist);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充季
|
||||
*
|
||||
* @param SeriesId
|
||||
*/
|
||||
private void fillSeason(String SeriesId) {
|
||||
JfClient.GetSeasons(SeriesId, new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(Items seasons) {
|
||||
fillItems(seasons.getItems());
|
||||
}
|
||||
}, errcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充集
|
||||
*
|
||||
* @param SeriesId
|
||||
* @param SeasonId
|
||||
* @return
|
||||
*/
|
||||
private void fillEpisodes(String SeriesId, String SeasonId) {
|
||||
JfClient.GetEpisodes(SeriesId, SeasonId, new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(Items episodes) {
|
||||
fillItems(episodes.getItems());
|
||||
}
|
||||
}, errcb);
|
||||
}
|
||||
|
||||
private void fillItems(List<Item> items) {
|
||||
JAdapter jAdapter = new JAdapter(items, false);
|
||||
V7LinearLayoutManager layoutManager = new V7LinearLayoutManager(mGridView.getContext());
|
||||
layoutManager.setOrientation(V7LinearLayoutManager.HORIZONTAL);
|
||||
jAdapter.setOnItemClickListener(this);
|
||||
mGridView.setVisibility(View.VISIBLE);
|
||||
mGridView.setLayoutManager(layoutManager);
|
||||
mGridView.setAdapter(jAdapter);
|
||||
dismissLoadingDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充演员表
|
||||
*
|
||||
* @param Peoples
|
||||
*/
|
||||
private void fillPeople(List<People> Peoples) {
|
||||
List<Item> Pitems = new ArrayList<>();
|
||||
for (People p : Peoples) {
|
||||
Item it = new Item();
|
||||
it.setId(p.getId());
|
||||
if (p.getType().equals("Director")) {
|
||||
it.setName("导演:" + p.getName());
|
||||
} else if (p.getType().equals("Actor")) {
|
||||
it.setName("演员:" + p.getName());
|
||||
} else {
|
||||
it.setName(p.getName());
|
||||
}
|
||||
it.setType(p.getType());
|
||||
it.setImageTags(new ImageTags());
|
||||
it.getImageTags().setPrimary(p.getPrimaryImageTag());
|
||||
Pitems.add(it);
|
||||
}
|
||||
|
||||
tvPeopleLayout.setVisibility(View.VISIBLE);
|
||||
JAdapter jAdapter = new JAdapter(Pitems, false);
|
||||
V7LinearLayoutManager layoutManager = new V7LinearLayoutManager(mPeopleGridView.getContext());
|
||||
layoutManager.setOrientation(V7LinearLayoutManager.HORIZONTAL);
|
||||
jAdapter.setOnItemClickListener(this);
|
||||
mPeopleGridView.setLayoutManager(layoutManager);
|
||||
mPeopleGridView.setAdapter(jAdapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充演员作品
|
||||
*
|
||||
* @param personid
|
||||
*/
|
||||
private void fillItemsByPerson(String personid) {
|
||||
String Term = "&SortBy=DateCreated&SortOrder=Descending&PersonIds=" + personid;
|
||||
Term += "&IncludeItemTypes=Movie,Series";
|
||||
JfClient.GetItemsByTerm(Term, new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(Items iitems) {
|
||||
((TextView) findViewById(R.id.tvListTitle)).setText("演员作品:");
|
||||
List<Item> items = iitems.getItems();
|
||||
JAdapter jAdapter = new JAdapter(items, false);
|
||||
V7GridLayoutManager layoutManager = new V7GridLayoutManager(mGridView.getContext(), 4);
|
||||
jAdapter.setOnItemClickListener(new JAdapter.OnItemClickListener() {
|
||||
@Override
|
||||
public void onClick(Item item) {
|
||||
String itemId = item.getId();
|
||||
Intent intent = new Intent(DetailActivity.this, DetailActivity.class);
|
||||
intent.putExtra("itemId", itemId);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
mGridView.setVisibility(View.VISIBLE);
|
||||
mGridView.setLayoutManager(layoutManager);
|
||||
mGridView.setAdapter(jAdapter);
|
||||
dismissLoadingDialog();
|
||||
}
|
||||
}, errcb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Item item) {
|
||||
String itemId = item.getId();
|
||||
String type = item.getType();
|
||||
Intent intent = null;
|
||||
if (type.equals("Season")) {
|
||||
intent = new Intent(this, DetailActivity.class);
|
||||
intent.putExtra("itemId", itemId);
|
||||
startActivity(intent);
|
||||
} else if (type.equals("Episode")) {
|
||||
JfClient.playList.clear();
|
||||
JAdapter JA = (JAdapter) mGridView.getAdapter();
|
||||
List<Item> ja = JA.getData();
|
||||
if (ja != null) {
|
||||
for (int i = 0; i < ja.size(); i++) {
|
||||
Video media = getMedia(ja.get(i));
|
||||
JfClient.playList.add(media);
|
||||
if (itemId.equals(media.Id)) {
|
||||
JfClient.playIndex = i;
|
||||
}
|
||||
}
|
||||
toVlcPlayer();
|
||||
}
|
||||
} else if (type.equals("Movie") || type.equals("Video")) {
|
||||
JfClient.playList.clear();
|
||||
JfClient.playList.add(getMedia(item));
|
||||
JfClient.playIndex = 0;
|
||||
toVlcPlayer();
|
||||
} else if (type.equals("Actor") || type.equals("Director")) {
|
||||
intent = new Intent(this, DetailActivity.class);
|
||||
intent.putExtra("itemId", itemId);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组合播放媒体
|
||||
*
|
||||
* @param item
|
||||
* @return
|
||||
*/
|
||||
public Video getMedia(Item item) {
|
||||
Video media = new Video();
|
||||
media.Id = item.getId();
|
||||
media.Name = item.getName();
|
||||
media.cover = "";
|
||||
media.Url = JfClient.GetPlayUrl(media.Id);
|
||||
if (item.getUserData() != null) {
|
||||
UserData userdata = item.getUserData();
|
||||
media.startPositionTicks = userdata.getPlaybackPositionTicks();
|
||||
}
|
||||
return media;
|
||||
}
|
||||
|
||||
public void toVlcPlayer() {
|
||||
Intent intent;
|
||||
if(JfClient.config.isExtensionPlayer()){
|
||||
String videourl = JfClient.playList.get(JfClient.playIndex).Url;
|
||||
Uri uri = Uri.parse(videourl);
|
||||
intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(uri,"video/mp4");
|
||||
}else{
|
||||
intent = new Intent(this, VlcPlayerActivity.class);
|
||||
}
|
||||
this.startActivity(intent);
|
||||
}
|
||||
|
||||
private JfClient.JJCallBack errcb = new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onError(String str) {
|
||||
ShowToask(str);
|
||||
dismissLoadingDialog();
|
||||
}
|
||||
};
|
||||
}
|
235
app/src/main/java/org/sifacai/vlcjellyfin/HomeActivity.java
Normal file
@ -0,0 +1,235 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.owen.tvrecyclerview.widget.V7LinearLayoutManager;
|
||||
|
||||
import org.sifacai.vlcjellyfin.Bean.Item;
|
||||
import org.sifacai.vlcjellyfin.Bean.Items;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HomeActivity extends BaseActivity {
|
||||
private final String TAG = "HomeActivity";
|
||||
private LinearLayout tvContiner;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.jellyfin_home);
|
||||
|
||||
disableActiveBarBack();
|
||||
|
||||
tvContiner = findViewById(R.id.tvItems);
|
||||
|
||||
JfClient.init(getApplication());
|
||||
}
|
||||
|
||||
private JfClient.JJCallBack connErr = new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onError(String str) {
|
||||
dismissLoadingDialog();
|
||||
ShowToask(str);
|
||||
showLoginDialog();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
initData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录框
|
||||
*/
|
||||
private void showLoginDialog() {
|
||||
Log.d(TAG, "showLoginDialog: 跳出登录框");
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
AlertDialog alert = builder.setTitle("登录")
|
||||
.setMessage("请输入登录信息")
|
||||
.setView(R.layout.dialog_login)
|
||||
.show();
|
||||
alert.setCanceledOnTouchOutside(false);
|
||||
EditText urlbox = alert.findViewById(R.id.dialog_login_url);
|
||||
EditText unbox = alert.findViewById(R.id.dialog_login_un);
|
||||
EditText pwbox = alert.findViewById(R.id.dialog_login_pw);
|
||||
CheckBox saveBox = alert.findViewById(R.id.dialog_login_save);
|
||||
TextView submit = alert.findViewById(R.id.dialog_login_submit);
|
||||
TextView cancel = alert.findViewById(R.id.dialog_login_cancel);
|
||||
urlbox.setText(JfClient.config.getJellyfinUrl());
|
||||
unbox.setText(JfClient.config.getUserName());
|
||||
pwbox.setText(JfClient.config.getPassWord());
|
||||
cancel.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
submit.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
showLoadingDialog("正在验证服务器地址!");
|
||||
JfClient.VerityServerUrl(urlbox.getText().toString(), new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(Boolean bool) {
|
||||
setLoadingText("正在验证用户名和密码!");
|
||||
JfClient.AuthenticateByName(unbox.getText().toString(), pwbox.getText().toString(), new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(Boolean bool) {
|
||||
dismissLoadingDialog();
|
||||
alert.dismiss();
|
||||
initView(); //加载首页
|
||||
}
|
||||
}, new JfClient.JJCallBack(){
|
||||
@Override
|
||||
public void onError(String str) {
|
||||
dismissLoadingDialog();
|
||||
ShowToask("用户名密码验证失败!");
|
||||
}
|
||||
}, saveBox.isChecked());
|
||||
|
||||
}
|
||||
}, new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onError(String str) {
|
||||
dismissLoadingDialog();
|
||||
ShowToask("服务器地址不正确!");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initData(){
|
||||
if (JfClient.AccessToken.equals("") || JfClient.UserId.equals("")) {
|
||||
showLoadingDialog("正在验证服务器地址!");
|
||||
JfClient.VerityServerUrl(JfClient.config.getJellyfinUrl(), new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(Boolean bool) {
|
||||
setLoadingText("正在验证用户名和密码!");
|
||||
JfClient.AuthenticateByName(JfClient.config.getUserName(), JfClient.config.getPassWord(), new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(Boolean bool) {
|
||||
dismissLoadingDialog();
|
||||
initView();
|
||||
}
|
||||
}, connErr, false);
|
||||
}
|
||||
}, connErr);
|
||||
}else{
|
||||
Log.d(TAG, "initData: 跳出");
|
||||
initView();
|
||||
}
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
showLoadingDialog("正在加载首页…………");
|
||||
tvContiner.removeAllViews();
|
||||
JfClient.GetViews(new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(Items views) {
|
||||
List<Item> items = views.getItems();
|
||||
addRowTvRecyclerView("我的媒体", items, true);
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
Item item = items.get(i);
|
||||
JfClient.GetLatest(item.getId(), new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(Items latests) {
|
||||
addRowTvRecyclerView("新的 " + item.getName(), latests.getItems(), false);
|
||||
}
|
||||
}, errcb);
|
||||
}
|
||||
dismissLoadingDialog();
|
||||
}
|
||||
}, errcb);
|
||||
JfClient.GetResume(new JfClient.JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(Items resumes) {
|
||||
for(Item it : resumes.getItems()){
|
||||
String SeriesName = it.getSeriesName() == null ? "" : it.getSeriesName() + "-";
|
||||
String SeasonName = it.getSeasonName() == null ? "" : it.getSeasonName() + "-";
|
||||
it.setName(SeriesName+SeasonName+it.getName());
|
||||
}
|
||||
addRowTvRecyclerView("最近播放", resumes.getItems(), false);
|
||||
}
|
||||
}, errcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加类别行
|
||||
*/
|
||||
private void addRowTvRecyclerView(String title, List<Item> items, boolean horizon) {
|
||||
JRecyclerView tvRecyclerView = (JRecyclerView) LayoutInflater.from(this)
|
||||
.inflate(R.layout.home_horizon_tvrecycler, null);
|
||||
((V7LinearLayoutManager) tvRecyclerView.getLayoutManager()).setOrientation(V7LinearLayoutManager.HORIZONTAL);
|
||||
|
||||
JAdapter jAdapter = new JAdapter(items, horizon);
|
||||
jAdapter.setOnItemClickListener(new JAdapter.OnItemClickListener() {
|
||||
@Override
|
||||
public void onClick(Item item) {
|
||||
String type = item.getType();
|
||||
String itemId = item.getId();
|
||||
Intent intent = null;
|
||||
if (type.equals("Folder") || type.equals("CollectionFolder")) {
|
||||
intent = new Intent(mAA, CollectionActivity.class);
|
||||
} else {
|
||||
intent = new Intent(mAA, DetailActivity.class);
|
||||
}
|
||||
intent.putExtra("itemId", itemId);
|
||||
mAA.startActivity(intent);
|
||||
}
|
||||
});
|
||||
tvRecyclerView.setAdapter(jAdapter);
|
||||
|
||||
TextView titleView = new TextView(tvRecyclerView.getContext());
|
||||
titleView.setText(title);
|
||||
tvContiner.addView(titleView);
|
||||
tvContiner.addView(tvRecyclerView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
exit();
|
||||
return false;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
private long exitTime = 0;
|
||||
|
||||
public void exit() {
|
||||
if ((System.currentTimeMillis() - exitTime) > 2000) {
|
||||
Toast.makeText(getApplicationContext(), "再按一次退出程序",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
exitTime = System.currentTimeMillis();
|
||||
} else {
|
||||
finish();
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
public JfClient.JJCallBack errcb = new JfClient.JJCallBack(){
|
||||
@Override
|
||||
public void onError(String str) {
|
||||
dismissLoadingDialog();
|
||||
ShowToask(str);
|
||||
}
|
||||
};
|
||||
}
|
147
app/src/main/java/org/sifacai/vlcjellyfin/JAdapter.java
Normal file
@ -0,0 +1,147 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.owen.tvrecyclerview.widget.TvRecyclerView;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import org.sifacai.vlcjellyfin.Bean.Item;
|
||||
import org.sifacai.vlcjellyfin.Bean.Items;
|
||||
import org.sifacai.vlcjellyfin.Bean.People;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class JAdapter extends RecyclerView.Adapter {
|
||||
private String TAG = "JAdapter:";
|
||||
private List<Item> items;
|
||||
private boolean horizon;
|
||||
|
||||
class VH extends RecyclerView.ViewHolder{
|
||||
public String id;
|
||||
public String type;
|
||||
public TextView tvName;
|
||||
public ImageView tvCover;
|
||||
public SeekBar tvPlayedPercentage;
|
||||
|
||||
public VH(View v) {
|
||||
super(v);
|
||||
id = "";
|
||||
type = "";
|
||||
tvName = v.findViewById(R.id.tvName);
|
||||
tvCover = v.findViewById(R.id.ivThumb);
|
||||
tvPlayedPercentage = v.findViewById(R.id.tvPlayedPercentage);
|
||||
}
|
||||
}
|
||||
|
||||
public JAdapter(List<Item> items) {
|
||||
this.items = items;
|
||||
this.horizon = false;
|
||||
}
|
||||
|
||||
public JAdapter(List<Item> items,Boolean horizon) {
|
||||
this.items = items;
|
||||
this.horizon = horizon;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v = null;
|
||||
if(horizon){
|
||||
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_h,parent,false);
|
||||
}else{
|
||||
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_v,parent,false);
|
||||
}
|
||||
return new VH(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
VH v = (VH)holder;
|
||||
Item item = items.get(position);
|
||||
v.id = item.getId();
|
||||
v.tvName.setText(item.getName());
|
||||
|
||||
if(item.getUserData() != null){
|
||||
int pp = (int) Math.round(item.getUserData().getPlayedPercentage());
|
||||
v.tvPlayedPercentage.setProgress(pp);
|
||||
v.tvPlayedPercentage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
v.type = item.getType();
|
||||
String imgUrl = JfClient.GetImgUrl(item.getId(),item.getImageTags().getPrimary());
|
||||
|
||||
if (!TextUtils.isEmpty(imgUrl)) {
|
||||
Picasso.get()
|
||||
.load(imgUrl)
|
||||
.placeholder(R.drawable.img_loading_placeholder)
|
||||
.error(R.drawable.img_loading_placeholder)
|
||||
.into(v.tvCover);
|
||||
} else {
|
||||
v.tvCover.setImageResource(R.drawable.img_loading_placeholder);
|
||||
}
|
||||
v.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
listener.onClick(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public List<Item> getData(){
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
int count = items.size();
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据
|
||||
* @param items
|
||||
*/
|
||||
public void setItems(List<Item> items){
|
||||
this.items = items;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加数据
|
||||
* @param items
|
||||
*/
|
||||
public void addItems(List<Item> items){
|
||||
int c = this.items.size();
|
||||
this.items.addAll(items);
|
||||
notifyItemRangeInserted(c,items.size());
|
||||
}
|
||||
|
||||
public void clearItems(){
|
||||
this.items = new ArrayList<>();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
//定义OnItemClickListener接口
|
||||
public interface OnItemClickListener {
|
||||
void onClick(Item item);
|
||||
}
|
||||
|
||||
public OnItemClickListener listener;
|
||||
|
||||
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
44
app/src/main/java/org/sifacai/vlcjellyfin/JRecyclerView.java
Normal file
@ -0,0 +1,44 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.animation.BounceInterpolator;
|
||||
|
||||
import com.owen.tvrecyclerview.widget.TvRecyclerView;
|
||||
|
||||
public class JRecyclerView extends TvRecyclerView {
|
||||
public JRecyclerView(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public JRecyclerView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public JRecyclerView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context){
|
||||
setOnItemListener(new TvRecyclerView.OnItemListener() {
|
||||
@Override
|
||||
public void onItemPreSelected(TvRecyclerView parent, View itemView, int position) {
|
||||
itemView.animate().scaleX(1.0f).scaleY(1.0f).setDuration(300).setInterpolator(new BounceInterpolator()).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSelected(TvRecyclerView parent, View itemView, int position) {
|
||||
itemView.animate().scaleX(1.05f).scaleY(1.05f).setDuration(300).setInterpolator(new BounceInterpolator()).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(TvRecyclerView parent, View itemView, int position) {}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
713
app/src/main/java/org/sifacai/vlcjellyfin/JfClient.java
Normal file
@ -0,0 +1,713 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.lzy.okgo.OkGo;
|
||||
import com.lzy.okgo.callback.AbsCallback;
|
||||
import com.lzy.okgo.model.HttpHeaders;
|
||||
import com.lzy.okgo.model.Response;
|
||||
import com.squareup.picasso.OkHttp3Downloader;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import org.sifacai.vlcjellyfin.Bean.Item;
|
||||
import org.sifacai.vlcjellyfin.Bean.Items;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class JfClient {
|
||||
public static final String TAG = "JellyfinClient";
|
||||
public static final String XEmbyAuthorization = "MediaBrowser Client=\"Vlc_J_TV\", Device=\"Vlc_J_TV\", DeviceId=\"TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgNi4xOyBXa\", Version=\"10.8.1\"";
|
||||
public static HttpHeaders headers;
|
||||
public static Config config;
|
||||
public static String UserId = "";
|
||||
public static String AccessToken = "";
|
||||
|
||||
public static int playIndex = 0; //当前播放
|
||||
public static ArrayList<Video> playList = new ArrayList<>(); //播放列表
|
||||
|
||||
public enum ReportType {
|
||||
playing,
|
||||
Progress,
|
||||
stop
|
||||
}
|
||||
|
||||
/**
|
||||
* description 忽略https证书验证
|
||||
*
|
||||
* @author yanzy
|
||||
* @version 1.0
|
||||
* @date 2021/9/8 14:42
|
||||
*/
|
||||
private static TrustManager[] getTrustManager() {
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{
|
||||
new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[]{};
|
||||
}
|
||||
}
|
||||
};
|
||||
return trustAllCerts;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* description 忽略https证书验证
|
||||
*`在这里插入代码片`
|
||||
* @author yanzy
|
||||
* @version 1.0
|
||||
* @date 2021/9/8 14:42
|
||||
*/
|
||||
public static SSLSocketFactory getSSLSocketFactory() {
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||
sslContext.init(null, getTrustManager(), new SecureRandom());
|
||||
return sslContext.getSocketFactory();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* description 忽略https证书验证
|
||||
*
|
||||
* @author yanzy
|
||||
* @version 1.0
|
||||
* @date 2021/9/8 14:42
|
||||
*/
|
||||
public static HostnameVerifier getHostnameVerifier() {
|
||||
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String s, SSLSession sslSession) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return hostnameVerifier;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化配置
|
||||
*
|
||||
* @param application
|
||||
*/
|
||||
public static void init(Application application) {
|
||||
config = new Config(application);
|
||||
SetHeaders();
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||
builder.sslSocketFactory(getSSLSocketFactory());
|
||||
builder.hostnameVerifier(getHostnameVerifier());
|
||||
builder.connectTimeout(5, TimeUnit.SECONDS);
|
||||
builder.readTimeout(3, TimeUnit.SECONDS);
|
||||
builder.writeTimeout(5, TimeUnit.SECONDS);
|
||||
OkGo.getInstance().init(application)
|
||||
.setOkHttpClient(builder.build())
|
||||
.setRetryCount(3);
|
||||
Picasso.setSingletonInstance(new Picasso.Builder(application.getBaseContext())
|
||||
.downloader(new OkHttp3Downloader(builder.build()))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回放报告
|
||||
*
|
||||
* @param type
|
||||
* @param id
|
||||
* @param PositionTicks
|
||||
*/
|
||||
public static void ReportPlayBackState(ReportType type, String id, long PositionTicks) {
|
||||
String url = config.getJellyfinUrl();
|
||||
if (type == ReportType.playing) {
|
||||
url += "/Sessions/Playing";
|
||||
} else if (type == ReportType.Progress) {
|
||||
url += "/Sessions/Playing/Progress";
|
||||
} else if (type == ReportType.stop) {
|
||||
url += "/Sessions/Playing/Stopped";
|
||||
}
|
||||
String reqstr = "{\"itemId\":\"" + id + "\",\"PositionTicks\":\"" + PositionTicks * 10000 + "\"}";
|
||||
SendPost(url, reqstr, new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
//回放报告
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取封面图url
|
||||
*
|
||||
* @param itemid
|
||||
* @param tagid
|
||||
* @return
|
||||
*/
|
||||
public static String GetImgUrl(String itemid, String tagid) {
|
||||
String url = config.getJellyfinUrl() + "/Items/" + itemid + "/Images/Primary";
|
||||
url += "?fillHeight=286&fillWidth=200&quality=96&tag=" + tagid;
|
||||
return url;
|
||||
}
|
||||
|
||||
public static String GetImgUrl(Item item) {
|
||||
if (item.getImageTags() == null) return "";
|
||||
if (item.getImageTags().getPrimary() == null || item.getImageTags().getPrimary().equals(""))
|
||||
return "";
|
||||
return GetImgUrl(item.getId(), item.getImageTags().getPrimary());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取播放url
|
||||
*
|
||||
* @param itemid
|
||||
* @return
|
||||
*/
|
||||
public static String GetPlayUrl(String itemid) {
|
||||
String playurl = config.getJellyfinUrl() + "/videos/" + itemid + "/stream.mp4?static=true";
|
||||
return playurl;
|
||||
}
|
||||
|
||||
public static void SearchByTerm(String term, int limit, JJCallBack scb, JJCallBack errcb) {
|
||||
String BaseUrl = config.getJellyfinUrl() + "/Users/" + UserId + "/Items?";
|
||||
BaseUrl += "Fields=PrimaryImageAspectRatio,CanDelete,BasicSyncInfo,MediaSourceCount";
|
||||
BaseUrl += "&Recursive=true&EnableTotalRecordCount=false&ImageTypeLimit=1&IncludePeople=false";
|
||||
BaseUrl += "&IncludeMedia=true&IncludeGenres=false&IncludeStudios=false&IncludeArtists=false";
|
||||
BaseUrl += "&Limit=" + limit;
|
||||
|
||||
String PersonUrl = config.getJellyfinUrl() + "/Persons?Fields=PrimaryImageAspectRatio%2CCanDelete%2CBasicSyncInfo%2CMediaSourceCount&Recursive=true";
|
||||
PersonUrl += "&EnableTotalRecordCount=false&ImageTypeLimit=1&IncludePeople=true&IncludeMedia=false";
|
||||
PersonUrl += "&IncludeGenres=false&IncludeStudios=false&IncludeArtists=false&userId=" + UserId;
|
||||
String personUrl = PersonUrl + "&searchTerm=" + term + "&Limit=" + limit;
|
||||
|
||||
String movieUrl = BaseUrl + "&searchTerm=" + term + "&IncludeItemTypes=Movie";
|
||||
String seriesUrl = BaseUrl + "&searchTerm=" + term + "&IncludeItemTypes=Series";
|
||||
String episodeUrl = BaseUrl + "&searchTerm=" + term + "&IncludeItemTypes=Episode";
|
||||
|
||||
JsonArray items = new JsonArray();
|
||||
new Thread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Items items = new Items();
|
||||
items.setItems(new ArrayList<>());
|
||||
String jsonstr = SendGet(movieUrl);
|
||||
Items moviejob = Utils.jsonToClass(jsonstr, Items.class);
|
||||
if (moviejob != null) {
|
||||
items.AddItems(moviejob.getItems());
|
||||
items.setTotalRecordCount(items.getTotalRecordCount() + moviejob.getTotalRecordCount());
|
||||
}
|
||||
|
||||
jsonstr = SendGet(seriesUrl);
|
||||
Items seriesobj = Utils.jsonToClass(jsonstr, Items.class);
|
||||
if (seriesobj != null) {
|
||||
items.AddItems(seriesobj.getItems());
|
||||
items.setTotalRecordCount(items.getTotalRecordCount() + seriesobj.getTotalRecordCount());
|
||||
}
|
||||
|
||||
jsonstr = SendGet(personUrl);
|
||||
Items personobj = Utils.jsonToClass(jsonstr, Items.class);
|
||||
if (personobj != null) {
|
||||
items.AddItems(personobj.getItems());
|
||||
items.setTotalRecordCount(items.getTotalRecordCount() + personobj.getTotalRecordCount());
|
||||
}
|
||||
scb.onSuccess(items);
|
||||
} catch (Exception e) {
|
||||
errcb.onError(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目附加部分
|
||||
*
|
||||
* @param itemid
|
||||
* @param cb
|
||||
*/
|
||||
public static void GetAddPart(String itemid, JJCallBack cb, JJCallBack errcb) {
|
||||
String AddPartUrl = config.getJellyfinUrl() + "/Videos/" + itemid + "/AdditionalParts?userId=" + UserId;
|
||||
SendGet(AddPartUrl, new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
try {
|
||||
Items items = Utils.jsonToClass(str, Items.class);
|
||||
cb.onSuccess(items);
|
||||
} catch (Exception e) {
|
||||
errcb.onError(e.getMessage());
|
||||
}
|
||||
}
|
||||
}, errcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取剧集
|
||||
*
|
||||
* @param seriesId 剧ID
|
||||
* @param seasonId 季ID
|
||||
* @param cb
|
||||
*/
|
||||
public static void GetEpisodes(String seriesId, String seasonId, JJCallBack cb, JJCallBack errcb) {
|
||||
String EpisodesUrl = config.getJellyfinUrl() + "/Shows/" + seriesId + "/Episodes?seasonId=" + seasonId;
|
||||
EpisodesUrl += "&userId=" + UserId;
|
||||
EpisodesUrl += "&Fields=ItemCounts,PrimaryImageAspectRatio,BasicSyncInfo,CanDelete,MediaSourceCount,Overview";
|
||||
|
||||
SendGet(EpisodesUrl, new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
try {
|
||||
Items items = Utils.jsonToClass(str, Items.class);
|
||||
cb.onSuccess(items);
|
||||
} catch (Exception e) {
|
||||
errcb.onError(e.getMessage());
|
||||
}
|
||||
}
|
||||
}, errcb);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取季(Seasons)数据
|
||||
*
|
||||
* @param seriesId 剧ID
|
||||
* @param cb
|
||||
*/
|
||||
public static void GetSeasons(String seriesId, JJCallBack cb, JJCallBack errcb) {
|
||||
String SeasonsUrl = config.getJellyfinUrl() + "/Shows/" + seriesId + "/Seasons?userId=" + UserId;
|
||||
SeasonsUrl += "&Fields=ItemCounts,PrimaryImageAspectRatio,BasicSyncInfo,MediaSourceCount";
|
||||
|
||||
SendGet(SeasonsUrl, new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
try {
|
||||
Items items = Utils.jsonToClass(str, Items.class);
|
||||
cb.onSuccess(items);
|
||||
} catch (Exception e) {
|
||||
errcb.onError(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
}, errcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件获取条目列表
|
||||
*
|
||||
* @param term 可以是 &Limit、&SortBy、&IncludeItemTypes、&PersonIds 等
|
||||
* @param scb
|
||||
* @param errcb
|
||||
*/
|
||||
public static void GetItemsByTerm(String term, JJCallBack scb, JJCallBack errcb) {
|
||||
String BaseUrl = config.getJellyfinUrl() + "/Users/" + UserId + "/Items?Recursive=true&StartIndex=0&CollapseBoxSetItems=false";
|
||||
BaseUrl += term;
|
||||
|
||||
SendGet(BaseUrl, new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
try {
|
||||
Items items = Utils.jsonToClass(str, Items.class);
|
||||
scb.onSuccess(items);
|
||||
} catch (Exception e) {
|
||||
errcb.onError(e.getMessage());
|
||||
}
|
||||
}
|
||||
}, errcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取合集条目
|
||||
*
|
||||
* @param parentId
|
||||
* @param type 类型 movie、tvshows……
|
||||
* @param sortBy 排序条件 评分、加入时间……
|
||||
* @param sortOrder 排序规则 升序、降序
|
||||
* @param limit 每页条数
|
||||
* @param page 页
|
||||
* @param cb
|
||||
*/
|
||||
public static void GetCollection(String parentId, String type, String sortBy, String sortOrder, int limit, int page, JJCallBack cb, JJCallBack errcb) {
|
||||
String itemsUrl = config.getJellyfinUrl() + "/Users/" + UserId + "/Items?ParentId=" + parentId + "&Limit=" + limit;
|
||||
itemsUrl += "&Recursive=true&Fields=PrimaryImageAspectRatio,BasicSyncInfo,Seasons,Episodes&ImageTypeLimit=1";
|
||||
itemsUrl += "&EnableImageTypes=Primary,Backdrop,Banner,Thumb";
|
||||
itemsUrl += "&SortBy=" + sortBy + ",SortName,ProductionYear&SortOrder=" + sortOrder;
|
||||
|
||||
if (type.equals("tvshows")) {
|
||||
itemsUrl += "&IncludeItemTypes=Series";
|
||||
} else if (type.equals("movies")) {
|
||||
itemsUrl += "&IncludeItemTypes=Movie";
|
||||
} else {
|
||||
itemsUrl += "&IncludeItemTypes=Movie,Series";
|
||||
}
|
||||
int startIndex = page * limit - limit;
|
||||
itemsUrl += "&StartIndex=" + startIndex;
|
||||
|
||||
SendGet(itemsUrl, new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
try {
|
||||
Items items = Utils.jsonToClass(str, Items.class);
|
||||
cb.onSuccess(items);
|
||||
} catch (Exception e) {
|
||||
errcb.onError(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
}, errcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新项目
|
||||
*
|
||||
* @param parentId
|
||||
* @param cb
|
||||
*/
|
||||
public static void GetLatest(String parentId, JJCallBack cb, JJCallBack errcb) {
|
||||
String lastestUrl = config.getJellyfinUrl() + "/Users/" + UserId + "/Items/Latest?";
|
||||
lastestUrl += "Limit=16&Fields=PrimaryImageAspectRatio%2CBasicSyncInfo%2CPath";
|
||||
lastestUrl += "&ImageTypeLimit=1&EnableImageTypes=Primary,Backdrop,Thumb";
|
||||
lastestUrl += "&ParentId=" + parentId;
|
||||
|
||||
SendGet(lastestUrl, new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
Type type = new TypeToken<List<Item>>() {
|
||||
}.getType();
|
||||
List<Item> rets = Utils.jsonToClass(str, type);
|
||||
if (null != rets) {
|
||||
Items items = new Items();
|
||||
items.setItems(rets);
|
||||
items.setTotalRecordCount(rets.size());
|
||||
cb.onSuccess(items);
|
||||
}
|
||||
}
|
||||
}, errcb);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近播放
|
||||
*
|
||||
* @param cb
|
||||
*/
|
||||
public static void GetResume(JJCallBack cb, JJCallBack err) {
|
||||
String resumeUrl = config.getJellyfinUrl() + "/Users/" + UserId + "/Items/Resume?";
|
||||
resumeUrl += "Limit=12&Recursive=true&Fields=PrimaryImageAspectRatio,BasicSyncInfo";
|
||||
resumeUrl += "&ImageTypeLimit=1&EnableImageTypes=Primary,Backdrop,Thumb";
|
||||
resumeUrl += "&EnableTotalRecordCount=false&MediaTypes=Video";
|
||||
|
||||
SendGet(resumeUrl, new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
try {
|
||||
Items items = Utils.jsonToClass(str, Items.class);
|
||||
cb.onSuccess(items);
|
||||
} catch (Exception e) {
|
||||
err.onError(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}, err);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取首页
|
||||
*
|
||||
* @param cb
|
||||
*/
|
||||
public static void GetViews(JJCallBack cb, JJCallBack err) {
|
||||
String viewsUrl = config.getJellyfinUrl() + "/Users/" + UserId + "/Views";
|
||||
SendGet(viewsUrl, new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
try {
|
||||
Items views = Utils.jsonToClass(str, Items.class);
|
||||
cb.onSuccess(views);
|
||||
} catch (Exception e) {
|
||||
err.onError(e.getMessage());
|
||||
}
|
||||
}
|
||||
}, err);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目详情
|
||||
*
|
||||
* @param itemid
|
||||
* @param cb
|
||||
*/
|
||||
public static void GetItemInfo(String itemid, JJCallBack cb, JJCallBack err) {
|
||||
String url = config.getJellyfinUrl() + "/Users/" + UserId + "/Items/" + itemid;
|
||||
SendGet(url, new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
try {
|
||||
Item item = Utils.jsonToClass(str, Item.class);
|
||||
cb.onSuccess(item);
|
||||
} catch (Exception e) {
|
||||
err.onError(e.getMessage());
|
||||
}
|
||||
}
|
||||
}, err);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户名密码
|
||||
*
|
||||
* @param username
|
||||
* @param password
|
||||
* @param cb
|
||||
*/
|
||||
public static void AuthenticateByName(String username, String password, JJCallBack cb, JJCallBack err, boolean saveUser) {
|
||||
if(username.equals("") || password.equals("")){
|
||||
err.onError("用户名和密码验证失败!");
|
||||
return;
|
||||
}
|
||||
String url = config.getJellyfinUrl() + "/Users/authenticatebyname";
|
||||
String reqjson = "{\"Username\":\"" + username + "\",\"Pw\":\"" + password + "\"}";
|
||||
SendPost(url, reqjson, new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
try {
|
||||
JsonObject userObj = new Gson().fromJson(str, JsonObject.class);
|
||||
if (userObj.has("User") && userObj.has("AccessToken")) {
|
||||
String userId = userObj.get("User").getAsJsonObject().get("Id").getAsString();
|
||||
String Token = userObj.get("AccessToken").getAsString();
|
||||
if (!Token.equals("")) {
|
||||
UserId = userId;
|
||||
AccessToken = Token;
|
||||
if (saveUser) {
|
||||
config.setUserName(username);
|
||||
config.setPassWord(password);
|
||||
}
|
||||
SetHeaders();
|
||||
cb.onSuccess(true);
|
||||
} else {
|
||||
err.onError("验证失败:" + str);
|
||||
}
|
||||
} else {
|
||||
err.onError("验证失败:" + str);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
err.onError("验证失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}, err);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
*
|
||||
* @param cb
|
||||
*/
|
||||
public static void GetUsers(JJCallBack cb, JJCallBack err) {
|
||||
String url = config.getJellyfinUrl() + "/users/public";
|
||||
SendGet(url, new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
JsonArray users = new Gson().fromJson(str, JsonArray.class);
|
||||
cb.onSuccess(users);
|
||||
}
|
||||
}, err);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证服务器地址
|
||||
*
|
||||
* @param url
|
||||
* @param cb
|
||||
*/
|
||||
public static void VerityServerUrl(String url, JJCallBack cb, JJCallBack err) {
|
||||
if (url.startsWith("http://") || url.startsWith("https://")) {
|
||||
SendGet(url + "/system/info/public", new JJCallBack() {
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
try {
|
||||
JsonObject serverInfo = new Gson().fromJson(str, JsonObject.class);
|
||||
String ServerId = "";
|
||||
ServerId = serverInfo.get("Id").getAsString();
|
||||
if (ServerId == null || ServerId.length() == 0) {
|
||||
err.onError("服务器连接失败!");
|
||||
} else {
|
||||
config.setJellyfinUrl(url);
|
||||
cb.onSuccess(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
err.onError(e.getMessage());
|
||||
}
|
||||
}
|
||||
}, err);
|
||||
} else {
|
||||
err.onError("服务器地址不正确!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求头
|
||||
*/
|
||||
public static void SetHeaders() {
|
||||
headers = new HttpHeaders();
|
||||
String xea = XEmbyAuthorization;
|
||||
if (null != AccessToken && AccessToken.length() > 0) {
|
||||
xea += ", Token=\"" + AccessToken + "\"";
|
||||
}
|
||||
headers.put("Accept", "application/json");
|
||||
headers.put("Accept-Language", "zh-CN,zh;q=0.9");
|
||||
headers.put("X-Emby-Authorization", xea);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get
|
||||
*
|
||||
* @param url
|
||||
* @param cb
|
||||
*/
|
||||
public static void SendGet(String url, JJCallBack cb, JJCallBack errcb) {
|
||||
OkGo.<String>get(url).headers(headers).execute(new AbsCallback<String>() {
|
||||
@Override
|
||||
public String convertResponse(okhttp3.Response response) throws IOException {
|
||||
String result = "";
|
||||
if (null != response.body()) {
|
||||
result = response.body().string();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Response<String> response) {
|
||||
cb.onSuccess(response.body());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Response<String> response) {
|
||||
if (errcb != null) {
|
||||
errcb.onError(response.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Post
|
||||
*
|
||||
* @param url
|
||||
* @param jsonStr
|
||||
* @param cb
|
||||
*/
|
||||
public static void SendPost(String url, String jsonStr, JJCallBack cb, JJCallBack errcb) {
|
||||
OkGo.<String>post(url).headers(headers).upJson(jsonStr).execute(new AbsCallback<String>() {
|
||||
@Override
|
||||
public String convertResponse(okhttp3.Response response) throws Throwable {
|
||||
String result = "";
|
||||
if (null != response.body()) {
|
||||
result = response.body().string();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Response<String> response) {
|
||||
cb.onSuccess(response.body());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Response<String> response) {
|
||||
if (errcb != null) {
|
||||
errcb.onError(response.body());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 阻塞式Get
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
public static String SendGet(String url) throws IOException {
|
||||
String response = "";
|
||||
response = OkGo.<String>get(url).headers(headers).execute().body().string();
|
||||
return response;
|
||||
}
|
||||
|
||||
public static class JJCallBack implements JCallBack {
|
||||
|
||||
@Override
|
||||
public void onSuccess(String str) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Boolean bool) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(JsonObject jsonObject) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(JsonArray jsonArray) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Item item) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Items items) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String str) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public interface JCallBack {
|
||||
void onSuccess(String str);
|
||||
|
||||
void onSuccess(Boolean bool);
|
||||
|
||||
void onSuccess(JsonObject jsonObject);
|
||||
|
||||
void onSuccess(JsonArray jsonArray);
|
||||
|
||||
void onSuccess(Item item);
|
||||
|
||||
void onSuccess(Items items);
|
||||
|
||||
void onError(String str);
|
||||
}
|
||||
}
|
106
app/src/main/java/org/sifacai/vlcjellyfin/PopMenu.java
Normal file
@ -0,0 +1,106 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PopMenu extends PopupWindow {
|
||||
|
||||
public class menu{
|
||||
public int groupid;
|
||||
public int id;
|
||||
public int orderid;
|
||||
public String name;
|
||||
public View v;
|
||||
}
|
||||
|
||||
private Context context;
|
||||
private ArrayList<menu> items;
|
||||
private View attView;
|
||||
private ScrollView contentView;
|
||||
private LinearLayout itemContiner;
|
||||
|
||||
public PopMenu(Context context, View attView) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
items = new ArrayList<>();
|
||||
|
||||
setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
setOutsideTouchable(true);
|
||||
setFocusable(true);
|
||||
setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||
contentView = (ScrollView) LayoutInflater.from(context).inflate(R.layout.popmenu,null);
|
||||
setContentView(contentView);
|
||||
itemContiner = contentView.findViewById(R.id.popitemContiner);
|
||||
this.attView = attView;
|
||||
setClippingEnabled(true);
|
||||
}
|
||||
|
||||
public menu add(int groupid,int id,int orderid,String name){
|
||||
menu m = new menu();
|
||||
m.groupid = groupid;
|
||||
m.id = id;
|
||||
m.orderid = orderid;
|
||||
m.name = name;
|
||||
View v = LayoutInflater.from(context).inflate(R.layout.popmenu_item,null);
|
||||
m.v = v;
|
||||
((TextView)v).setText(name);
|
||||
items.add(m);
|
||||
itemContiner.addView(v);
|
||||
return m;
|
||||
}
|
||||
|
||||
public void show(){
|
||||
if(items.size() > 12){
|
||||
setHeight(500);
|
||||
}
|
||||
contentView.measure(makeDropDownMeasureSpec(getWidth())
|
||||
,makeDropDownMeasureSpec(getHeight()));
|
||||
int offx = 0;
|
||||
int offy = contentView.getMeasuredHeight() + attView.getHeight() + 2;
|
||||
showAsDropDown(attView,offx,-offy);
|
||||
}
|
||||
|
||||
public void show(int index){
|
||||
show();
|
||||
// if(index >=0 && index < items.size()) {
|
||||
// items.get(index).v.requestFocus();
|
||||
// }
|
||||
for (menu m:items) {
|
||||
if(m.id == index){
|
||||
m.v.requestFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void show(String name){
|
||||
show();
|
||||
for (menu m:items) {
|
||||
if(m.name.equals(name)){
|
||||
m.v.requestFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResourceType")
|
||||
private static int makeDropDownMeasureSpec(int measureSpec) {
|
||||
int mode;
|
||||
if (measureSpec == ViewGroup.LayoutParams.WRAP_CONTENT) {
|
||||
mode = View.MeasureSpec.UNSPECIFIED;
|
||||
} else {
|
||||
mode = View.MeasureSpec.EXACTLY;
|
||||
}
|
||||
return View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.getSize(measureSpec), mode);
|
||||
}
|
||||
}
|
128
app/src/main/java/org/sifacai/vlcjellyfin/SearchActivity.java
Normal file
@ -0,0 +1,128 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.telecom.Call;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.owen.tvrecyclerview.widget.TvRecyclerView;
|
||||
import com.owen.tvrecyclerview.widget.V7GridLayoutManager;
|
||||
|
||||
import org.sifacai.vlcjellyfin.Bean.Item;
|
||||
import org.sifacai.vlcjellyfin.Bean.Items;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SearchActivity extends BaseActivity implements JAdapter.OnItemClickListener {
|
||||
private TvRecyclerView mGridView;
|
||||
private JAdapter adapter;
|
||||
private final int limit = 24;
|
||||
private String BaseUrl;
|
||||
|
||||
private EditText searchTermEdit;
|
||||
private ImageView searchBtn;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_search);
|
||||
|
||||
if (JfClient.UserId.equals("") || JfClient.AccessToken.equals("")) {
|
||||
finish();
|
||||
}
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (null != actionBar) {
|
||||
ImageView acb = actionBar.getCustomView().findViewById(R.id.actionBar_searchBtn);
|
||||
if (null != acb) {
|
||||
acb.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
mGridView = findViewById(R.id.mGridView);
|
||||
V7GridLayoutManager v7GridLayoutManager = new V7GridLayoutManager(this, 6);
|
||||
mGridView.setLayoutManager(v7GridLayoutManager);
|
||||
mGridView.setItemAnimator(null); //防崩溃
|
||||
|
||||
adapter = new JAdapter(new ArrayList<>());
|
||||
adapter.setOnItemClickListener(this);
|
||||
mGridView.setAdapter(adapter);
|
||||
|
||||
searchTermEdit = findViewById(R.id.searchTermEdit);
|
||||
searchBtn = findViewById(R.id.searchBtn);
|
||||
searchBtn.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String st = searchTermEdit.getText().toString().trim();
|
||||
if (st.length() > 0) {
|
||||
adapter.clearItems();
|
||||
Search(st);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
searchTermEdit.requestFocus();
|
||||
}
|
||||
|
||||
private void Search(String searchTerm) {
|
||||
showLoadingDialog("搜索中………………");
|
||||
JfClient.SearchByTerm(searchTerm,16,new JfClient.JJCallBack(){
|
||||
@Override
|
||||
public void onSuccess(Items items) {
|
||||
mAA.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
adapter.addItems(items.getItems());
|
||||
}
|
||||
});
|
||||
dismissLoadingDialog();
|
||||
}
|
||||
},new JfClient.JJCallBack(){
|
||||
@Override
|
||||
public void onError(String str) {
|
||||
ShowToask("搜索时发生错误:" + str);
|
||||
dismissLoadingDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fillItems(List<Item> items) {
|
||||
mAA.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
adapter.addItems(items);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(Item item) {
|
||||
String itemId = item.getId();
|
||||
String type = item.getType();
|
||||
Intent intent = null;
|
||||
switch (type) {
|
||||
case "Series":
|
||||
case "Season":
|
||||
case "Episode":
|
||||
case "Movie":
|
||||
case "Video":
|
||||
case "Person":
|
||||
intent = new Intent(this, DetailActivity.class);
|
||||
intent.putExtra("itemId", itemId);
|
||||
this.startActivity(intent);
|
||||
break;
|
||||
default:
|
||||
ShowToask("未知类型!");
|
||||
}
|
||||
}
|
||||
}
|
110
app/src/main/java/org/sifacai/vlcjellyfin/Utils.java
Normal file
@ -0,0 +1,110 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.lzy.okgo.OkGo;
|
||||
import com.lzy.okgo.callback.AbsCallback;
|
||||
import com.lzy.okgo.callback.Callback;
|
||||
import com.lzy.okgo.callback.StringCallback;
|
||||
import com.lzy.okgo.model.HttpHeaders;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class Utils {
|
||||
|
||||
/**
|
||||
* 标准时间转换
|
||||
*
|
||||
* @param utcTime
|
||||
* @return
|
||||
*/
|
||||
public static String UtcToLocal(String utcTime) {
|
||||
if(utcTime == null || utcTime.length() == 0) return "";
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'");
|
||||
df.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
String dt = "";
|
||||
try {
|
||||
Date date = df.parse(utcTime);
|
||||
SimpleDateFormat sdt = new SimpleDateFormat("yyyy-MM-dd");
|
||||
dt = sdt.format(date);
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return dt;
|
||||
}
|
||||
|
||||
public static int getPixelsFromDp(Activity context, int i) {
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
context.getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
return (i * metrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
|
||||
}
|
||||
|
||||
public static <T> T jsonToClass(String jsonstr, Type tClass) {
|
||||
if (jsonstr != null && jsonstr.length() > 0) {
|
||||
//Gson gson = new GsonBuilder().registerTypeAdapterFactory(new NullStringToEmptyAdapterFactory()).create();
|
||||
Gson gson = new Gson();
|
||||
return gson.fromJson(jsonstr, tClass);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class NullStringToEmptyAdapterFactory<T> implements TypeAdapterFactory {
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||
Class<T> rawType = (Class<T>) type.getRawType();
|
||||
if (rawType != String.class) {
|
||||
return null;
|
||||
}
|
||||
return (TypeAdapter<T>) new StringNullAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
public static class StringNullAdapter extends TypeAdapter<String> {
|
||||
@Override
|
||||
public String read(JsonReader reader) throws IOException {
|
||||
// TODO Auto-generated method stub
|
||||
if (reader.peek() == JsonToken.NULL) {
|
||||
reader.nextNull();
|
||||
return "";
|
||||
}
|
||||
return reader.nextString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter writer, String value) throws IOException {
|
||||
// TODO Auto-generated method stub
|
||||
if (value == null) {
|
||||
writer.nullValue();
|
||||
return;
|
||||
}
|
||||
writer.value(value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
12
app/src/main/java/org/sifacai/vlcjellyfin/Video.java
Normal file
@ -0,0 +1,12 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
public class Video {
|
||||
public String Id = "";
|
||||
public String Url = null;
|
||||
public String cover = "";
|
||||
public String Name = "";
|
||||
public long startPositionTicks = 0L;
|
||||
public long PositionTicks = 0L;
|
||||
}
|
619
app/src/main/java/org/sifacai/vlcjellyfin/VlcPlayerActivity.java
Normal file
@ -0,0 +1,619 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.videolan.libvlc.Dialog;
|
||||
import org.videolan.libvlc.LibVLC;
|
||||
import org.videolan.libvlc.Media;
|
||||
import org.videolan.libvlc.MediaPlayer;
|
||||
import org.videolan.libvlc.util.VLCVideoLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class VlcPlayerActivity extends BaseActivity implements MediaPlayer.EventListener
|
||||
, View.OnClickListener {
|
||||
private static final String TAG = "VLC播放器";
|
||||
public final int Type_SubtitleTrack = 0;
|
||||
public final int Type_AudioTrack = 1;
|
||||
public final int Type_Playlist = 2;
|
||||
public final int Type_Scale = 3;
|
||||
public final int Type_Speed = 4;
|
||||
|
||||
private Activity mActivity;
|
||||
private MediaPlayer mediaPlayer;
|
||||
private LibVLC libVLC;
|
||||
private VLCVideoLayout vlcVideoLayout;
|
||||
|
||||
private FrameLayout Controller; //总控
|
||||
private LinearLayout ControllerTop;
|
||||
private LinearLayout ControllerBottom;
|
||||
private LinearLayout ControllerBottomTop;
|
||||
private LinearLayout ControllerBottomBottom;
|
||||
private TextView videoTitle;
|
||||
private TextView currTime;
|
||||
private TextView countTime;
|
||||
private TextView speedBtn;
|
||||
private TextView scaleBtn;
|
||||
private ImageView playPauseBtn;
|
||||
private ImageView stopBtn;
|
||||
private ImageView subTracksBtn;
|
||||
private ImageView audioTracksBtn;
|
||||
private ImageView playListBtn;
|
||||
private ImageView pauseFlag;
|
||||
private SeekBar currPostion;
|
||||
private ProgressBar loading;
|
||||
|
||||
private PopMenu playListMenu = null; //播放列表
|
||||
private PopMenu subTrackMenu = null; //字幕菜单
|
||||
private PopMenu audioTrackMenu = null; //单轨菜单
|
||||
private PopMenu scaleTypeMenu = null; //缩放菜单
|
||||
private PopMenu speedMenu = null; //播放速率菜单
|
||||
|
||||
private float speedRate[] = {0.5f, 1.0f, 1.5f, 2.0f}; //倍速播放列表
|
||||
|
||||
private int updateTimeCount = 0;
|
||||
private int ReportCount = 30;
|
||||
private Video currItem; //当前播放项目
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_vlc_player);
|
||||
|
||||
mActivity = this;
|
||||
initVlc();
|
||||
initController();
|
||||
play();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化播放器
|
||||
*/
|
||||
private void initVlc() {
|
||||
List<String> vlcoptions = new ArrayList<>();
|
||||
vlcoptions.add("-v");
|
||||
vlcVideoLayout = findViewById(R.id.VideoView);
|
||||
libVLC = new LibVLC(this,vlcoptions);
|
||||
Dialog.setCallbacks(libVLC,callbacks);
|
||||
|
||||
mediaPlayer = new MediaPlayer(libVLC);
|
||||
mediaPlayer.attachViews(vlcVideoLayout, null, true, false);
|
||||
mediaPlayer.setEventListener(this);
|
||||
}
|
||||
|
||||
Dialog.Callbacks callbacks = new Dialog.Callbacks() {
|
||||
@Override
|
||||
public void onDisplay(Dialog.ErrorMessage dialog) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplay(Dialog.LoginDialog dialog) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplay(Dialog.QuestionDialog dialog) {
|
||||
if(dialog.getQuestionType() == 1){
|
||||
dialog.postAction(1);
|
||||
}else {
|
||||
Log.d(TAG, "onDisplay_QuestionDialog: " + dialog.getQuestionType() + " : " + dialog.getTitle() + " : " + dialog.getText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplay(Dialog.ProgressDialog dialog) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(Dialog dialog) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressUpdate(Dialog.ProgressDialog dialog) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onEvent(MediaPlayer.Event event) {
|
||||
switch (event.type) {
|
||||
case MediaPlayer.Event.Playing: //媒体打开成功
|
||||
Hide();
|
||||
pauseFlag.setVisibility(View.GONE);
|
||||
Log.d(TAG, "onEvent: Playing");
|
||||
ReportPlayState(JfClient.ReportType.playing);
|
||||
initMenu();
|
||||
break;
|
||||
case MediaPlayer.Event.Paused: //暂停
|
||||
pauseFlag.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
case MediaPlayer.Event.Stopped:
|
||||
Log.d(TAG, "onEvent: Stopped");
|
||||
ReportPlayState(JfClient.ReportType.stop);
|
||||
playNext();
|
||||
break;
|
||||
case MediaPlayer.Event.Opening: //媒体打开
|
||||
Log.d(TAG, "onEvent: Opening");
|
||||
break;
|
||||
case MediaPlayer.Event.Buffering: //媒体加载public float getBuffering() 获取加载视频流的进度0-100
|
||||
int Buffering = (int) event.getBuffering();
|
||||
if (Buffering >= 100) {
|
||||
loading.setVisibility(View.GONE);
|
||||
}else{
|
||||
if(loading.getVisibility() == View.GONE){
|
||||
loading.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MediaPlayer.Event.EndReached://媒体播放结束
|
||||
Log.d(TAG, "onEvent: EndReached");
|
||||
break;
|
||||
case MediaPlayer.Event.EncounteredError://媒体播放错误
|
||||
Log.d(TAG, "onEvent: EncounteredError");
|
||||
ShowToask("播放错误!");
|
||||
stop();
|
||||
break;
|
||||
case MediaPlayer.Event.TimeChanged://视频时间变化
|
||||
updateTimeCount += event.getTimeChanged() - currItem.PositionTicks;
|
||||
currItem.PositionTicks = event.getTimeChanged();
|
||||
if(updateTimeCount > 20000){
|
||||
updateTimeCount = 0;
|
||||
ReportPlayState(JfClient.ReportType.Progress);
|
||||
}
|
||||
break;
|
||||
case MediaPlayer.Event.PositionChanged://视频总时长的百分比
|
||||
break;
|
||||
case MediaPlayer.Event.SeekableChanged:
|
||||
Log.d(TAG, "onEvent: SeekableChanged:" + event.getSeekable());
|
||||
break;
|
||||
case MediaPlayer.Event.PausableChanged:
|
||||
Log.d(TAG, "onEvent: PausableChanged");
|
||||
break;
|
||||
case MediaPlayer.Event.LengthChanged:
|
||||
Log.d(TAG, "onEvent: LengthChanged");
|
||||
break;
|
||||
case MediaPlayer.Event.Vout://当图像输出
|
||||
Log.d(TAG, "onEvent: Vout");
|
||||
break;
|
||||
case MediaPlayer.Event.ESAdded:
|
||||
case MediaPlayer.Event.ESDeleted:
|
||||
case MediaPlayer.Event.ESSelected:
|
||||
Log.d(TAG, "onEvent: ES:" + event.type + ":" + event.getEsChangedType() + ":" + event.getEsChangedID());
|
||||
break;
|
||||
case MediaPlayer.Event.RecordChanged:
|
||||
Log.d(TAG, "onEvent: RecordChanged");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化控制器
|
||||
*/
|
||||
private void initController() {
|
||||
Controller = findViewById(R.id.Controller); // 总布局
|
||||
ControllerTop = findViewById(R.id.ControllerTop);
|
||||
ControllerBottom = findViewById(R.id.ControllerBottom);
|
||||
ControllerBottomTop = findViewById(R.id.ControllerBottomTop);
|
||||
ControllerBottomBottom = findViewById(R.id.ControllerBottomBottom);
|
||||
videoTitle = findViewById(R.id.videoTitle); // 标题
|
||||
currTime = findViewById(R.id.currTime);
|
||||
countTime = findViewById(R.id.countTime);
|
||||
speedBtn = findViewById(R.id.speedBtn);
|
||||
scaleBtn = findViewById(R.id.scaleBtn);
|
||||
playPauseBtn = findViewById(R.id.playPauseBtn);
|
||||
stopBtn = findViewById(R.id.stopBtn);
|
||||
pauseFlag = findViewById(R.id.pauseFlag);
|
||||
currPostion = findViewById(R.id.currPostion);
|
||||
loading = findViewById(R.id.loading);
|
||||
playPauseBtn.setOnClickListener(this);
|
||||
stopBtn.setOnClickListener(this);
|
||||
pauseFlag.setOnClickListener(this);
|
||||
currPostion.setOnKeyListener(new View.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(View view, int i, KeyEvent keyEvent) {
|
||||
boolean rv = false;
|
||||
int action = keyEvent.getAction();
|
||||
if(action == 1) { //按键up
|
||||
int keycode = keyEvent.getKeyCode();
|
||||
if (keycode == KeyEvent.KEYCODE_DPAD_RIGHT) {
|
||||
rv = setTimeOnSeekBar(currItem.PositionTicks + (long) (mediaPlayer.getLength() * 0.05));
|
||||
} else if (keycode == KeyEvent.KEYCODE_DPAD_LEFT) {
|
||||
rv = setTimeOnSeekBar(currItem.PositionTicks - (long) (mediaPlayer.getLength() * 0.05));
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化字幕和音轨菜单
|
||||
*/
|
||||
private void initMenu() {
|
||||
MediaPlayer.TrackDescription[] subTrackList = mediaPlayer.getSpuTracks();
|
||||
MediaPlayer.TrackDescription[] audioTrackList = mediaPlayer.getAudioTracks();
|
||||
|
||||
playListBtn = findViewById(R.id.playListBtn);
|
||||
subTracksBtn = findViewById(R.id.subTracksBtn);
|
||||
audioTracksBtn = findViewById(R.id.audioTracksBtn);
|
||||
if (JfClient.playList.size() > 1) {
|
||||
initPlayListMenu();
|
||||
} else {
|
||||
playListBtn.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (null != subTrackList && subTrackList.length > 1) {
|
||||
initSubTrackMenu(subTrackList);
|
||||
} else {
|
||||
subTracksBtn.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (null != audioTrackList && audioTrackList.length > 1) {
|
||||
initAudioTrackMenu(audioTrackList);
|
||||
} else {
|
||||
audioTracksBtn.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
//初始化缩放键
|
||||
scaleBtn.setOnClickListener(this);
|
||||
scaleBtn.setText(getVlcScaleTypeName(mediaPlayer.getVideoScale().name()));
|
||||
scaleTypeMenu = new PopMenu(this, scaleBtn);
|
||||
MediaPlayer.ScaleType[] scaleTypes = MediaPlayer.ScaleType.values();
|
||||
for (int i = 0; i < scaleTypes.length; i++) {
|
||||
PopMenu.menu menu = scaleTypeMenu.add(Type_Scale, i, i, getVlcScaleTypeName(scaleTypes[i].name()));
|
||||
final int Si = i;
|
||||
menu.v.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
scaleTypeMenu.dismiss();
|
||||
if (mediaPlayer.getVideoScale() != scaleTypes[Si]) {
|
||||
mediaPlayer.setVideoScale(scaleTypes[Si]);
|
||||
scaleBtn.setText(getVlcScaleTypeName(mediaPlayer.getVideoScale().name()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//初始化速率键
|
||||
speedBtn.setOnClickListener(this);
|
||||
speedBtn.setText(String.valueOf(mediaPlayer.getRate()));
|
||||
speedMenu = new PopMenu(this, speedBtn);
|
||||
for (int i = 0; i < speedRate.length; i++) {
|
||||
PopMenu.menu menu = speedMenu.add(Type_Speed, i, i, String.valueOf(speedRate[i]));
|
||||
final int Fi = i;
|
||||
menu.v.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
speedMenu.dismiss();
|
||||
if (mediaPlayer.getRate() != speedRate[Fi]) {
|
||||
mediaPlayer.setRate(speedRate[Fi]);
|
||||
speedBtn.setText(String.valueOf(mediaPlayer.getRate()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void initPlayListMenu() {
|
||||
playListMenu = new PopMenu(this, playListBtn); //new PopupMenu(this,playListBtn);
|
||||
for (int i = 0; i < JfClient.playList.size(); i++) {
|
||||
PopMenu.menu m = playListMenu.add(Type_Playlist, i, i, JfClient.playList.get(i).Name);
|
||||
m.v.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
playListMenu.dismiss();
|
||||
if (m.id != JfClient.playIndex) {
|
||||
ReportPlayState(JfClient.ReportType.stop);
|
||||
JfClient.playIndex = m.id;
|
||||
play();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
playListBtn.setOnClickListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化字幕菜单
|
||||
*
|
||||
* @param subTrackList
|
||||
*/
|
||||
private void initSubTrackMenu(MediaPlayer.TrackDescription[] subTrackList) {
|
||||
subTrackMenu = new PopMenu(this, subTracksBtn);
|
||||
for (int i = 0; i < subTrackList.length; i++) {
|
||||
PopMenu.menu m = subTrackMenu.add(Type_SubtitleTrack, subTrackList[i].id, i, subTrackList[i].name);
|
||||
m.v.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
subTrackMenu.dismiss();
|
||||
if (m.id != mediaPlayer.getSpuTrack()) {
|
||||
mediaPlayer.setSpuTrack(m.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
subTracksBtn.setOnClickListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化音轨菜单
|
||||
*
|
||||
* @param audioTrackList
|
||||
*/
|
||||
private void initAudioTrackMenu(MediaPlayer.TrackDescription[] audioTrackList) {
|
||||
audioTrackMenu = new PopMenu(this, audioTracksBtn);
|
||||
for (int i = 0; i < audioTrackList.length; i++) {
|
||||
PopMenu.menu m = audioTrackMenu.add(Type_AudioTrack, audioTrackList[i].id, i, audioTrackList[i].name);
|
||||
m.v.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
audioTrackMenu.dismiss();
|
||||
if (m.id != mediaPlayer.getAudioTrack()) {
|
||||
mediaPlayer.setAudioTrack(m.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
audioTracksBtn.setOnClickListener(this);
|
||||
}
|
||||
|
||||
private Handler mhandler = new Handler(Looper.getMainLooper());
|
||||
private Runnable mUpdateSeekBar = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSeekBar(currItem.PositionTicks);
|
||||
mhandler.postDelayed(mUpdateSeekBar,1000);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 显示控制器
|
||||
*/
|
||||
public void Show() {
|
||||
Show(6);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示控制器
|
||||
*/
|
||||
public void Show(int sec) {
|
||||
if (ControllerTop.getVisibility() == View.GONE) {
|
||||
ControllerTop.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (ControllerBottom.getVisibility() == View.GONE) {
|
||||
ControllerBottom.setVisibility(View.VISIBLE);
|
||||
mhandler.postDelayed(mUpdateSeekBar,0);
|
||||
playPauseBtn.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏控制器
|
||||
*/
|
||||
public void Hide() {
|
||||
if (ControllerTop.getVisibility() == View.VISIBLE) {
|
||||
ControllerTop.setVisibility(View.GONE);
|
||||
}
|
||||
if (ControllerBottom.getVisibility() == View.VISIBLE) {
|
||||
ControllerBottom.setVisibility(View.GONE);
|
||||
mhandler.removeCallbacks(mUpdateSeekBar);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进度条时间
|
||||
*/
|
||||
public void setSeekBar(Long p) {
|
||||
if (ControllerBottom.getVisibility() == View.VISIBLE) {
|
||||
if(null != mediaPlayer && mediaPlayer.getLength() > 0){
|
||||
double i = (double) p / 1000;
|
||||
long duration = mediaPlayer.getLength();
|
||||
if (duration > 0) {
|
||||
long pos = 1000L * p / duration;
|
||||
currPostion.setProgress((int) pos);
|
||||
}
|
||||
currTime.setText(TrickToTime(p));
|
||||
countTime.setText(TrickToTime(duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始播放
|
||||
*/
|
||||
public void play() {
|
||||
if (JfClient.playList.size() > 0) {
|
||||
if (JfClient.playIndex < JfClient.playList.size()) {
|
||||
currItem = JfClient.playList.get(JfClient.playIndex);
|
||||
videoTitle.setText(currItem.Name);
|
||||
Media media = getMedia();
|
||||
mediaPlayer.setMedia(media);
|
||||
media.release();
|
||||
mediaPlayer.play();
|
||||
}
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
private Media getMedia(){
|
||||
Uri uri = Uri.parse(currItem.Url);
|
||||
Media media = new Media(libVLC,uri);
|
||||
media.setHWDecoderEnabled(JfClient.config.isHAACC(),JfClient.config.isFORCE_HAACC());
|
||||
//media.addOption(":codec=mediacodec_ndk,mediacodec_jni,none"); //硬件加速
|
||||
if(!JfClient.config.isPlayStartInBegin()){
|
||||
long startTime = currItem.startPositionTicks / 10000L / 1000L;
|
||||
if( startTime > 60) media.addOption(":start-time=" + startTime); //设置开始位置
|
||||
}
|
||||
return media;
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放下一集
|
||||
*/
|
||||
public void playNext() {
|
||||
if (JfClient.playIndex < (JfClient.playList.size() - 1)) {
|
||||
ReportPlayState(JfClient.ReportType.stop);
|
||||
JfClient.playIndex += 1;
|
||||
play();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止播放并结束Activity
|
||||
*/
|
||||
public void stop() {
|
||||
ReportPlayState(JfClient.ReportType.stop);
|
||||
mediaPlayer.stop();
|
||||
mediaPlayer.release();
|
||||
libVLC.release();
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放或暂停
|
||||
*/
|
||||
public void playOrpause() {
|
||||
if (mediaPlayer.isPlaying()) {
|
||||
mediaPlayer.pause();
|
||||
pauseFlag.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mediaPlayer.play();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置播放器进度
|
||||
*/
|
||||
public boolean setTimeOnSeekBar(Long p) {
|
||||
if (p < mediaPlayer.getLength() && p > 0) {
|
||||
mediaPlayer.setTime(p,true);
|
||||
setSeekBar(p);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 进度转时间
|
||||
*
|
||||
* @param trick
|
||||
* @return
|
||||
*/
|
||||
public static String TrickToTime(long trick) {
|
||||
String time = "";
|
||||
long totalSeconds = trick / 1000;
|
||||
long seconds = totalSeconds % 60;
|
||||
long minutes = (totalSeconds / 60) % 60;
|
||||
long hours = totalSeconds / 3600;
|
||||
|
||||
time = String.format("%02d:%02d:%02d", hours, minutes, seconds);
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (ControllerBottom.getVisibility() == View.GONE) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_DPAD_UP:
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN:
|
||||
Show();
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
mediaPlayer.setTime(mediaPlayer.getTime() + 30000);
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
mediaPlayer.setTime(mediaPlayer.getTime() - 10000);
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_ENTER:
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
playOrpause();
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_ESCAPE:
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
stop();
|
||||
return true;
|
||||
//退出
|
||||
}
|
||||
} else {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_ESCAPE:
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
Hide();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
int id = view.getId();
|
||||
if (id == R.id.playPauseBtn) {
|
||||
playOrpause();
|
||||
} else if (id == R.id.stopBtn) {
|
||||
stop();
|
||||
} else if (id == R.id.subTracksBtn) {
|
||||
subTrackMenu.show(mediaPlayer.getSpuTrack());
|
||||
} else if (id == R.id.audioTracksBtn) {
|
||||
audioTrackMenu.show(mediaPlayer.getAudioTrack());
|
||||
} else if (id == R.id.playListBtn) {
|
||||
playListMenu.show(JfClient.playIndex);
|
||||
} else if (id == R.id.scaleBtn) {
|
||||
scaleTypeMenu.show(getVlcScaleTypeName(mediaPlayer.getVideoScale().name()));
|
||||
} else if (id == R.id.speedBtn) {
|
||||
speedMenu.show(String.valueOf(mediaPlayer.getRate()));
|
||||
}
|
||||
}
|
||||
|
||||
private void ReportPlayState(JfClient.ReportType type) {
|
||||
JfClient.ReportPlayBackState(type,currItem.Id,currItem.PositionTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
mhandler.removeCallbacksAndMessages(null);
|
||||
super.finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据缩放类型取名称
|
||||
*
|
||||
* @param scaleName
|
||||
* @return
|
||||
*/
|
||||
public String getVlcScaleTypeName(String scaleName) {
|
||||
switch (scaleName) {
|
||||
case "SURFACE_BEST_FIT":
|
||||
return "自动";
|
||||
case "SURFACE_FIT_SCREEN":
|
||||
return "适应屏幕";
|
||||
case "SURFACE_FILL":
|
||||
return "满屏";
|
||||
case "SURFACE_16_9":
|
||||
return "16:9";
|
||||
case "SURFACE_4_3":
|
||||
return "4:3";
|
||||
case "SURFACE_ORIGINAL":
|
||||
return "原始";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
30
app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
5
app/src/main/res/drawable/anim_loading.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:drawable="@drawable/icon_loading"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%" />
|
BIN
app/src/main/res/drawable/avatar.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable/banner.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
app/src/main/res/drawable/bg_blueradiance.png
Normal file
After Width: | Height: | Size: 2.7 MiB |
BIN
app/src/main/res/drawable/bg_purplehaze.png
Normal file
After Width: | Height: | Size: 878 KiB |
@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M9,16h2L11,8L9,8v8zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM13,16h2L15,8h-2v8z"/>
|
||||
</vector>
|
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M17.51,3.87L15.73,2.1 5.84,12l9.9,9.9 1.77,-1.77L9.38,12l8.13,-8.13z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_outline_audiotrack_48.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4L14,7h4L18,3h-6zM10,19c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_outline_pause_48.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
|
||||
</vector>
|
@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M9,16h2L11,8L9,8v8zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM13,16h2L15,8h-2v8z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_outline_play_arrow_48.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M10,8.64L15.27,12 10,15.36V8.64M8,5v14l11,-7L8,5z"/>
|
||||
</vector>
|
@ -0,0 +1,5 @@
|
||||
<vector android:height="96dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="96dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M10,16.5l6,-4.5 -6,-4.5zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
|
||||
</vector>
|
@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M4,10h12v2L4,12zM4,6h12v2L4,8zM4,14h8v2L4,16zM14,14v6l5,-3z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_outline_search_48.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_outline_skip_next_48.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6,18l8.5,-6L6,6v12zM8,9.86L11.03,12 8,14.14L8,9.86zM16,6h2v12h-2z"/>
|
||||
</vector>
|
@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6l-8.5,6zM16,14.14L12.97,12 16,9.86v4.28z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_outline_stop_48.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M16,8v8H8V8h8m2,-2H6v12h12V6z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_outline_subtitles_48.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,6h16v12zM6,10h2v2L6,12zM6,14h8v2L6,16zM16,14h2v2h-2zM10,10h8v2h-8z"/>
|
||||
</vector>
|
BIN
app/src/main/res/drawable/icon_img_placeholder.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable/icon_loading.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
15
app/src/main/res/drawable/img_loading_placeholder.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="#36ffffff" />
|
||||
<corners android:radius="1dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap
|
||||
android:antialias="true"
|
||||
android:gravity="center"
|
||||
android:src="@drawable/icon_img_placeholder" />
|
||||
</item>
|
||||
</layer-list>
|
15
app/src/main/res/drawable/played_percentage.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@android:id/background">
|
||||
<shape>
|
||||
<solid android:color="#00ffffff" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:id="@android:id/progress">
|
||||
<clip>
|
||||
<shape>
|
||||
<solid android:color="#1890ff" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
</layer-list>
|
16
app/src/main/res/drawable/popmenu_focus.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_focused="true">
|
||||
<shape>
|
||||
<corners android:radius="0dp" />
|
||||
<stroke android:width="1dp" android:color="@color/color_0CADE2" />
|
||||
<solid android:color="@color/color_3D3D3D" />
|
||||
</shape>
|
||||
</item>
|
||||
<!-- <item android:state_focused="false">-->
|
||||
<!-- <shape>-->
|
||||
<!-- <corners android:radius="0dp" />-->
|
||||
<!-- <solid android:color="@color/color_6C3D3D3D"/>-->
|
||||
<!-- </shape>-->
|
||||
<!-- </item>-->
|
||||
</selector>
|
16
app/src/main/res/drawable/shape_user_focus.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_focused="true">
|
||||
<shape>
|
||||
<corners android:radius="1dp" />
|
||||
<stroke android:width="1dp" android:color="@color/color_BD0CADE2" />
|
||||
<solid android:color="@color/color_6CFFFFFF" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_focused="false">
|
||||
<shape>
|
||||
<corners android:radius="1dp" />
|
||||
<solid android:color="#00000000"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
16
app/src/main/res/drawable/shape_user_focus_vholder.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_focused="true">
|
||||
<shape>
|
||||
<corners android:radius="1dp" />
|
||||
<stroke android:width="2dp" android:color="@color/color_0CADE2" />
|
||||
<solid android:color="@color/color_6CFFFFFF" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_focused="false">
|
||||
<shape>
|
||||
<corners android:radius="1dp" />
|
||||
<solid android:color="@color/color_6C3D3D3D"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
BIN
app/src/main/res/drawable/touchicon144.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
84
app/src/main/res/layout/activebar_custom.xml
Normal file
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/title_height"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/actionBar_back"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:padding="6dp"
|
||||
android:src="@drawable/ic_outline_arrow_back_ios_48" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/actionBar_banner"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="fitStart"
|
||||
app:srcCompat="@drawable/banner" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/actionBar_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="@dimen/button_margin_left"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text=""
|
||||
android:textSize="18dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/actionBar_titleTip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="@dimen/button_margin_left"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text=""
|
||||
android:textSize="18dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/actionBar_searchBtn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:padding="6dp"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:src="@drawable/ic_outline_search_48" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/actionBar_sortBtn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="@dimen/button_margin_left"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:gravity="center"
|
||||
android:minWidth="100dp"
|
||||
android:text="排序"
|
||||
android:textSize="18dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/actionBar_setBtn"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="@dimen/button_margin_left"
|
||||
android:padding="6dp"
|
||||
android:src="@drawable/avatar"
|
||||
android:focusableInTouchMode="true"
|
||||
android:focusable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@drawable/shape_user_focus" />
|
||||
|
||||
</LinearLayout>
|
25
app/src/main/res/layout/activity_collection.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".CollectionActivity">
|
||||
|
||||
<org.sifacai.vlcjellyfin.JRecyclerView
|
||||
android:id="@+id/mGridView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingLeft="26dp"
|
||||
android:paddingTop="@dimen/padding_border_10"
|
||||
android:paddingRight="@dimen/padding_border_10"
|
||||
android:paddingBottom="@dimen/padding_border_10"
|
||||
app:tv_horizontalSpacingWithMargins="@dimen/vh_space_width"
|
||||
app:tv_selectedItemIsCentered="true"
|
||||
app:tv_verticalSpacingWithMargins="@dimen/vh_space_width" />
|
||||
|
||||
|
||||
</LinearLayout>
|
125
app/src/main/res/layout/activity_detail.xml
Normal file
@ -0,0 +1,125 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/padding_border"
|
||||
tools:context=".DetailActivity">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fadingEdge="vertical"
|
||||
android:scrollbars="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tvCover"
|
||||
android:layout_width="420dp"
|
||||
android:layout_height="620dp"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/icon_img_placeholder" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="@dimen/padding_border"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingRight="@dimen/padding_border"
|
||||
android:paddingBottom="6dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="38dp"
|
||||
android:text="标题:"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="30dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDetails"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="200dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textSize="20dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvListTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:text="播放列表:" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tvPlay"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginLeft="50dp"
|
||||
android:background="@drawable/shape_user_focus_vholder"
|
||||
android:focusable="true"
|
||||
android:padding="10dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_outline_play_circle_outline_128"
|
||||
android:visibility="gone" />
|
||||
|
||||
<org.sifacai.vlcjellyfin.JRecyclerView
|
||||
android:id="@+id/mGridView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:padding="@dimen/padding_border"
|
||||
android:visibility="gone"
|
||||
app:tv_horizontalSpacingWithMargins="@dimen/vh_space_width"
|
||||
app:tv_selectedItemIsCentered="true"
|
||||
app:tv_verticalSpacingWithMargins="@dimen/vh_space_width" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tvPersonLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" >
|
||||
|
||||
<TextView
|
||||
android:text="演员表:"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"/>
|
||||
|
||||
<org.sifacai.vlcjellyfin.JRecyclerView
|
||||
android:id="@+id/mPersonGridView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="312dp"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:padding="@dimen/padding_border"
|
||||
app:tv_horizontalSpacingWithMargins="@dimen/vh_space_width"
|
||||
app:tv_selectedItemIsCentered="true"
|
||||
app:tv_verticalSpacingWithMargins="@dimen/vh_space_width" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
49
app/src/main/res/layout/activity_search.xml
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:gravity="center"
|
||||
android:paddingLeft="60dp"
|
||||
android:paddingRight="60dp"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<EditText
|
||||
android:id="@+id/searchTermEdit"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:maxLength="20"
|
||||
android:inputType="text"
|
||||
android:lines="1"
|
||||
android:focusable="true"
|
||||
android:tooltipText="关键字" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/searchBtn"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginLeft="@dimen/button_margin_left"
|
||||
android:focusable="true"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:src="@drawable/ic_outline_search_48" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<org.sifacai.vlcjellyfin.JRecyclerView
|
||||
android:id="@+id/mGridView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:padding="@dimen/padding_border"
|
||||
app:tv_horizontalSpacingWithMargins="@dimen/vh_space_width"
|
||||
app:tv_selectedItemIsCentered="true"
|
||||
app:tv_verticalSpacingWithMargins="@dimen/vh_space_width" />
|
||||
|
||||
</LinearLayout>
|
186
app/src/main/res/layout/activity_vlc_player.xml
Normal file
@ -0,0 +1,186 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".VlcPlayerActivity">
|
||||
|
||||
<org.videolan.libvlc.util.VLCVideoLayout
|
||||
android:id="@+id/VideoView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/Controller"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ControllerTop"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
android:background="@color/color_99000000"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="30dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingRight="30dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/videoTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="标题"
|
||||
android:textColor="#99FFFFFF"
|
||||
android:textSize="36dp" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ControllerBottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="@color/color_99000000"
|
||||
android:gravity="bottom"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ControllerBottomTop"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currTime"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="60dp"
|
||||
android:gravity="center"
|
||||
android:text="00:00:00"
|
||||
android:textSize="26dp" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/currPostion"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:focusable="true"
|
||||
android:max="1000"
|
||||
android:maxHeight="26dp"
|
||||
android:minHeight="26dp"
|
||||
android:progress="0" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/countTime"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="60dp"
|
||||
android:gravity="center"
|
||||
android:text="00:00:00"
|
||||
android:textSize="26dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ControllerBottomBottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="20dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/playPauseBtn"
|
||||
android:layout_width="@dimen/button_width"
|
||||
android:layout_height="@dimen/button_height"
|
||||
android:layout_marginLeft="@dimen/button_margin_left"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_outline_play_arrow_48" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/stopBtn"
|
||||
android:layout_width="@dimen/button_width"
|
||||
android:layout_height="@dimen/button_height"
|
||||
android:layout_marginLeft="@dimen/button_margin_left"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_outline_stop_48" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/speedBtn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/button_height"
|
||||
android:layout_marginLeft="@dimen/button_margin_left"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:minWidth="@dimen/button_width"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp"
|
||||
android:textSize="@dimen/title_size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/scaleBtn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/button_height"
|
||||
android:layout_marginLeft="@dimen/button_margin_left"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:minWidth="@dimen/button_width"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp"
|
||||
android:textSize="@dimen/title_size" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/subTracksBtn"
|
||||
android:layout_width="@dimen/button_width"
|
||||
android:layout_height="@dimen/button_height"
|
||||
android:layout_marginLeft="@dimen/button_margin_left"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_outline_subtitles_48" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/audioTracksBtn"
|
||||
android:layout_width="@dimen/button_width"
|
||||
android:layout_height="@dimen/button_height"
|
||||
android:layout_marginLeft="@dimen/button_margin_left"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_outline_audiotrack_48" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/playListBtn"
|
||||
android:layout_width="@dimen/button_width"
|
||||
android:layout_height="@dimen/button_height"
|
||||
android:layout_marginLeft="@dimen/button_margin_left"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_outline_playlist_play_48" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/pauseFlag"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_baseline_pause_circle_outline_48"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_gravity="center" />
|
||||
</FrameLayout>
|
73
app/src/main/res/layout/dialog_login.xml
Normal file
@ -0,0 +1,73 @@
|
||||
<?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"
|
||||
android:padding="@dimen/padding_border"
|
||||
android:paddingTop="0dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/dialog_login_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/dialog_text_height"
|
||||
android:inputType="text"
|
||||
android:lines="1"
|
||||
android:nextFocusDown="@id/dialog_login_un"
|
||||
android:hint="服务器地址"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/dialog_login_un"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/dialog_text_height"
|
||||
android:inputType="text"
|
||||
android:lines="1"
|
||||
android:nextFocusDown="@id/dialog_login_pw"
|
||||
android:hint="用户名"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/dialog_login_pw"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/dialog_text_height"
|
||||
android:inputType="text"
|
||||
android:lines="1"
|
||||
android:nextFocusDown="@id/dialog_login_save"
|
||||
android:hint="密码"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/dialog_login_save"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/dialog_text_height"
|
||||
android:focusable="true"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:nextFocusDown="@id/dialog_login_submit"
|
||||
android:text="保存用户" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/dialog_text_height"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_login_submit"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:focusable="true"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:text="确定"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_login_cancel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:focusable="true"
|
||||
android:background="@drawable/shape_user_focus"
|
||||
android:text="取消"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
14
app/src/main/res/layout/home_horizon_tvrecycler.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.sifacai.vlcjellyfin.JRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:padding="20dp"
|
||||
app:tv_layoutManager="V7LinearLayoutManager"
|
||||
app:tv_horizontalSpacingWithMargins="10dp"
|
||||
app:tv_selectedItemIsCentered="true">
|
||||
|
||||
</org.sifacai.vlcjellyfin.JRecyclerView>
|
55
app/src/main/res/layout/item_h.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/shape_user_focus_vholder"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/vh_padding_border">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="@dimen/horizon_cover_width"
|
||||
android:layout_height="@dimen/horizon_cover_height">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivThumb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="0dp"
|
||||
android:scaleType="fitXY" />
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="@color/color_99000000"
|
||||
android:padding="1dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/title_height"
|
||||
android:ellipsize="marquee"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:padding="0dp"
|
||||
android:gravity="center"
|
||||
android:singleLine="true" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/tvPlayedPercentage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:layout_gravity="bottom"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingStart="0dp"
|
||||
android:thumb="@null"
|
||||
android:progressDrawable="@drawable/played_percentage"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
55
app/src/main/res/layout/item_v.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/shape_user_focus_vholder"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/vh_padding_border">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="@dimen/vertical_cover_width"
|
||||
android:layout_height="@dimen/vertical_cover_height">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivThumb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="0dp"
|
||||
android:scaleType="fitXY" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="@color/color_99000000"
|
||||
android:padding="1dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/title_height"
|
||||
android:ellipsize="marquee"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:padding="0dp"
|
||||
android:gravity="center"
|
||||
android:singleLine="true" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/tvPlayedPercentage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:layout_gravity="bottom"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingStart="0dp"
|
||||
android:thumb="@null"
|
||||
android:progressDrawable="@drawable/played_percentage"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
29
app/src/main/res/layout/jellyfin_home.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".HomeActivity">
|
||||
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
android:fadingEdge="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tvItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/padding_border_10"
|
||||
android:paddingRight="@dimen/padding_border_10"/>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
28
app/src/main/res/layout/loading_alert.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:background="@color/color_6CFFFFFF"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar1"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/progressText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/title_size"
|
||||
android:text=""
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
16
app/src/main/res/layout/popmenu.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="0dp"
|
||||
android:layout_margin="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/popitemContiner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="0dp"
|
||||
android:orientation="vertical"
|
||||
android:padding="0dp" />
|
||||
|
||||
</ScrollView>
|
12
app/src/main/res/layout/popmenu_item.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:minWidth="100dp"
|
||||
android:maxWidth="300dp"
|
||||
android:padding="10dp"
|
||||
android:layout_margin="0dp"
|
||||
android:background="@drawable/popmenu_focus"
|
||||
android:focusable="true" >
|
||||
|
||||
</TextView>
|
36
app/src/main/res/menu/activebar_menu.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:orderInCategory="0"
|
||||
android:title="设置选项:" />
|
||||
|
||||
<item
|
||||
android:id="@+id/actionBar_option_PlayStartInBegin"
|
||||
android:orderInCategory="6"
|
||||
android:checkable="true"
|
||||
android:title="从头开始播放" />
|
||||
|
||||
<item
|
||||
android:id="@+id/actionBar_option_HAACC"
|
||||
android:orderInCategory="7"
|
||||
android:checkable="true"
|
||||
android:title="硬解解码" />
|
||||
|
||||
<item
|
||||
android:id="@+id/actionBar_option_FORCE_HAACC"
|
||||
android:orderInCategory="8"
|
||||
android:checkable="true"
|
||||
android:title="强制硬解解码" />
|
||||
|
||||
<item
|
||||
android:id="@+id/actionBar_option_ExtensionPlayer"
|
||||
android:orderInCategory="9"
|
||||
android:checkable="true"
|
||||
android:title="调用外部播放器" />
|
||||
|
||||
<item
|
||||
android:id="@+id/activeBar_option_logout"
|
||||
android:orderInCategory="99"
|
||||
android:title="注销" />
|
||||
|
||||
</menu>
|
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 7.6 KiB |
37
app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
|
||||
<color name="color_6200EE">#6200EE</color>
|
||||
<color name="color_3700B3">#3700B3</color>
|
||||
<color name="color_03DAC5">#03DAC5</color>
|
||||
<color name="color_1890FF">#1890FF</color>
|
||||
<color name="color_FFB800">#FF6600</color>
|
||||
<color name="color_00FF0A">#00FF0A</color>
|
||||
<color name="color_CBF46A">#CBF46A</color>
|
||||
<color name="color_FF90CA09">#90CA09</color>
|
||||
<color name="color_BD0CADE2">#BD0CADE2</color>
|
||||
<color name="color_FF0057">#FF0057</color>
|
||||
<color name="color_FF5F00">#FF5F00</color>
|
||||
<color name="color_3D3D3D">#3D3D3D</color>
|
||||
<color name="color_32364E">#32364E</color>
|
||||
<color name="color_6632364E">#6632364E</color>
|
||||
<color name="color_CCFFFFFF">#BBFFFFFF</color>
|
||||
<color name="color_0CADE2">#0CADE2</color>
|
||||
<color name="color_6CFFFFFF">#6CFFFFFF</color>
|
||||
<color name="color_66000000">#66000000</color>
|
||||
<color name="color_99000000">#99000000</color>
|
||||
<color name="color_CC000000">#CC000000</color>
|
||||
<color name="color_02F8E1">#02F8E1</color>
|
||||
<color name="color_353744">#353744</color>
|
||||
<color name="color_CC353744">#CC353744</color>
|
||||
<color name="color_6C3D3D3D">#6c3d3d3d</color>
|
||||
<color name="color_BBFFFFFF">#bbffffff</color>
|
||||
<color name="color_FFFFFF">#ffffffff</color>
|
||||
</resources>
|
36
app/src/main/res/values/dimens.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="title_height">32dp</dimen>
|
||||
<dimen name="title_size">22dp</dimen>
|
||||
|
||||
<dimen name="horizon_cover_width">400dp</dimen>
|
||||
<dimen name="horizon_cover_height">228dp</dimen>
|
||||
<dimen name="vertical_cover_width">180dp</dimen>
|
||||
<dimen name="vertical_cover_height">270dp</dimen>
|
||||
|
||||
<dimen name="focus_border">1dp</dimen>
|
||||
<dimen name="radius">5dp</dimen>
|
||||
|
||||
<dimen name="mGrid_padding_left">20dp</dimen>
|
||||
<dimen name="mGrid_padding_top">20dp</dimen>
|
||||
<dimen name="mGrid_padding_right">20dp</dimen>
|
||||
<dimen name="mGrid_padding_bottom">20dp</dimen>
|
||||
|
||||
<dimen name="padding_border_10">10dp</dimen>
|
||||
<dimen name="padding_border">20dp</dimen>
|
||||
<dimen name="vh_space_width">10dp</dimen>
|
||||
<dimen name="vh_padding_border">2dp</dimen>
|
||||
|
||||
<dimen name="drawable_padding_s">6dp</dimen>
|
||||
<dimen name="drawable_padding_b">12dp</dimen>
|
||||
|
||||
<!-- 登录框 -->
|
||||
<dimen name="dialog_text_height">48dp</dimen>
|
||||
|
||||
<!-- 播放控制器 -->
|
||||
<dimen name="time_text_size">26dp</dimen>
|
||||
<dimen name="button_height">60dp</dimen>
|
||||
<dimen name="button_width">60dp</dimen>
|
||||
|
||||
<dimen name="button_margin_left">26dp</dimen>
|
||||
</resources>
|
3
app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">VlcJellyfin</string>
|
||||
</resources>
|
26
app/src/main/res/values/theme.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="jellyfin" parent="Theme.AppCompat">
|
||||
<!-- hide title bar -->
|
||||
<!-- <item name="windowNoTitle">true</item>-->
|
||||
<!-- <item name="android:background">@drawable/bg_purplehaze</item>-->
|
||||
<item name="android:windowBackground">@drawable/bg_blueradiance</item>
|
||||
<item name="actionBarStyle">@style/JyfActionBar</item>
|
||||
|
||||
<item name="android:textSize">22dp</item>
|
||||
<!-- 默认 Button,TextView的文字颜色 -->
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
<!-- 默认 EditView 输入框字体的颜色 -->
|
||||
<!-- <item name="android:editTextColor">#E6E6FA</item>-->
|
||||
<!-- RadioButton checkbox等控件的文字 -->
|
||||
<!-- <item name="android:textColorPrimaryDisableOnly">#1C71A9</item>-->
|
||||
<!-- 应用的主要文字颜色,actionBar的标题文字默认使用该颜色 -->
|
||||
<item name="android:textColorPrimary">@android:color/white</item>
|
||||
<!-- 辅助的文字颜色,一般比textColorPrimary的颜色弱一点,用于一些弱化的表示 -->
|
||||
<item name="android:textColorSecondary">#ffffff</item>
|
||||
|
||||
</style>
|
||||
<style name="JyfActionBar" parent="@android:style/Widget.Holo.Light.ActionBar">
|
||||
<!-- <item name="android:background">#00000000</item>-->
|
||||
</style>
|
||||
</resources>
|
13
app/src/main/res/xml/backup_rules.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
19
app/src/main/res/xml/data_extraction_rules.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
@ -0,0 +1,17 @@
|
||||
package org.sifacai.vlcjellyfin;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
9
build.gradle
Normal file
@ -0,0 +1,9 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '7.2.2' apply false
|
||||
id 'com.android.library' version '7.2.2' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|