Major import from DSub for stability and quality
This commit is contained in:
parent
486b02f2e2
commit
596fab757c
|
@ -9,19 +9,19 @@ package com.handmark.pulltorefresh.library;
|
||||||
public final class R {
|
public final class R {
|
||||||
public static final class id {
|
public static final class id {
|
||||||
public static final int pullFromStart = 0x7f060001;
|
public static final int pullFromStart = 0x7f060001;
|
||||||
public static final int pull_to_refresh_progress = 0x7f06007f;
|
public static final int pull_to_refresh_progress = 0x7f060081;
|
||||||
public static final int rotate = 0x7f060007;
|
public static final int rotate = 0x7f060007;
|
||||||
public static final int both = 0x7f060003;
|
public static final int both = 0x7f060003;
|
||||||
public static final int webview = 0x7f06000a;
|
public static final int webview = 0x7f06000a;
|
||||||
public static final int pull_to_refresh_text = 0x7f060080;
|
public static final int pull_to_refresh_text = 0x7f060082;
|
||||||
public static final int pullDownFromTop = 0x7f060005;
|
public static final int pullDownFromTop = 0x7f060005;
|
||||||
public static final int gridview = 0x7f060009;
|
public static final int gridview = 0x7f060009;
|
||||||
public static final int pullUpFromBottom = 0x7f060006;
|
public static final int pullUpFromBottom = 0x7f060006;
|
||||||
public static final int scrollview = 0x7f06000b;
|
public static final int scrollview = 0x7f06000b;
|
||||||
public static final int pullFromEnd = 0x7f060002;
|
public static final int pullFromEnd = 0x7f060002;
|
||||||
public static final int pull_to_refresh_image = 0x7f06007e;
|
public static final int pull_to_refresh_image = 0x7f060080;
|
||||||
public static final int pull_to_refresh_sub_text = 0x7f060081;
|
public static final int pull_to_refresh_sub_text = 0x7f060083;
|
||||||
public static final int fl_inner = 0x7f06007d;
|
public static final int fl_inner = 0x7f06007f;
|
||||||
public static final int flip = 0x7f060008;
|
public static final int flip = 0x7f060008;
|
||||||
public static final int disabled = 0x7f060000;
|
public static final int disabled = 0x7f060000;
|
||||||
public static final int manualOnly = 0x7f060004;
|
public static final int manualOnly = 0x7f060004;
|
||||||
|
@ -41,8 +41,8 @@ public final class R {
|
||||||
public static final int pull_to_refresh_from_bottom_release_label = 0x7f080004;
|
public static final int pull_to_refresh_from_bottom_release_label = 0x7f080004;
|
||||||
}
|
}
|
||||||
public static final class layout {
|
public static final class layout {
|
||||||
public static final int pull_to_refresh_header_vertical = 0x7f030018;
|
public static final int pull_to_refresh_header_vertical = 0x7f030019;
|
||||||
public static final int pull_to_refresh_header_horizontal = 0x7f030017;
|
public static final int pull_to_refresh_header_horizontal = 0x7f030018;
|
||||||
}
|
}
|
||||||
public static final class styleable {
|
public static final class styleable {
|
||||||
public static final int PullToRefresh_ptrDrawableStart = 7;
|
public static final int PullToRefresh_ptrDrawableStart = 7;
|
||||||
|
@ -67,10 +67,10 @@ public final class R {
|
||||||
public static final int PullToRefresh_ptrHeaderSubTextColor = 3;
|
public static final int PullToRefresh_ptrHeaderSubTextColor = 3;
|
||||||
}
|
}
|
||||||
public static final class drawable {
|
public static final class drawable {
|
||||||
public static final int indicator_bg_top = 0x7f020045;
|
public static final int indicator_bg_top = 0x7f020046;
|
||||||
public static final int indicator_bg_bottom = 0x7f020044;
|
public static final int indicator_bg_bottom = 0x7f020045;
|
||||||
public static final int default_ptr_flip = 0x7f02000c;
|
public static final int default_ptr_flip = 0x7f02000c;
|
||||||
public static final int indicator_arrow = 0x7f020043;
|
public static final int indicator_arrow = 0x7f020044;
|
||||||
public static final int default_ptr_rotate = 0x7f02000d;
|
public static final int default_ptr_rotate = 0x7f02000d;
|
||||||
}
|
}
|
||||||
public static final class attr {
|
public static final class attr {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle" >
|
||||||
|
|
||||||
|
<gradient
|
||||||
|
android:angle="90"
|
||||||
|
android:endColor="#00000000"
|
||||||
|
android:startColor="#80000000"
|
||||||
|
android:type="linear" />
|
||||||
|
|
||||||
|
</shape>
|
|
@ -26,88 +26,7 @@
|
||||||
a:paddingBottom="12dip"
|
a:paddingBottom="12dip"
|
||||||
a:paddingTop="12dip" >
|
a:paddingTop="12dip" >
|
||||||
|
|
||||||
<ImageView
|
<include layout="@layout/media_buttons" />
|
||||||
a:id="@+id/download_toggle_list"
|
|
||||||
a:layout_width="0dip"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:paddingRight="4dip"
|
|
||||||
a:src="?attr/media_toggle" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_shuffle"
|
|
||||||
a:layout_width="0dip"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:paddingLeft="4dip"
|
|
||||||
a:src="?attr/media_shuffle" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_previous"
|
|
||||||
a:layout_width="0dp"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_previous" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_start"
|
|
||||||
a:layout_width="0dp"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_play" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_pause"
|
|
||||||
a:layout_width="0dp"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_pause" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_stop"
|
|
||||||
a:layout_width="0dp"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_stop" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_next"
|
|
||||||
a:layout_width="0dp"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_next" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_repeat"
|
|
||||||
a:layout_width="0dip"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_repeat_off" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_star"
|
|
||||||
a:layout_width="0dip"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:gravity="center_vertical"
|
|
||||||
a:src="?attr/star_hollow" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
@ -64,98 +64,7 @@
|
||||||
<include layout="@layout/download_playlist" />
|
<include layout="@layout/download_playlist" />
|
||||||
</com.thejoshwa.ultrasonic.androidapp.util.MyViewFlipper>
|
</com.thejoshwa.ultrasonic.androidapp.util.MyViewFlipper>
|
||||||
|
|
||||||
<LinearLayout
|
<include layout="@layout/media_buttons" />
|
||||||
a:layout_width="fill_parent"
|
|
||||||
a:layout_height="wrap_content"
|
|
||||||
a:layout_marginTop="0dip"
|
|
||||||
a:orientation="horizontal"
|
|
||||||
a:paddingBottom="12dip"
|
|
||||||
a:paddingTop="12dip" >
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_toggle_list"
|
|
||||||
a:layout_width="0dip"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:paddingRight="4dip"
|
|
||||||
a:src="?attr/media_toggle" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_shuffle"
|
|
||||||
a:layout_width="0dip"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:paddingLeft="4dip"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_shuffle" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_previous"
|
|
||||||
a:layout_width="0dp"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_previous" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_start"
|
|
||||||
a:layout_width="0dp"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_play" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_pause"
|
|
||||||
a:layout_width="0dp"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_pause" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_stop"
|
|
||||||
a:layout_width="0dp"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_stop" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_next"
|
|
||||||
a:layout_width="0dp"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_next" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_repeat"
|
|
||||||
a:layout_width="0dip"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/media_repeat_off" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
a:id="@+id/download_star"
|
|
||||||
a:layout_width="0dip"
|
|
||||||
a:layout_height="match_parent"
|
|
||||||
a:layout_gravity="center_vertical"
|
|
||||||
a:layout_weight="1"
|
|
||||||
a:focusable="true"
|
|
||||||
a:src="?attr/star_hollow" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<include layout="@layout/download_slider" />
|
<include layout="@layout/download_slider" />
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:padding="10dp" >
|
android:padding="6dp" >
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/select_album_select"
|
android:id="@+id/select_album_select"
|
||||||
|
|
|
@ -1,47 +1,45 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
xmlns:a="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
a:orientation="vertical"
|
android:orientation="vertical"
|
||||||
a:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
a:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
a:background="@drawable/album_art_background"
|
android:padding="16dip">
|
||||||
a:padding="16dip">
|
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
a:id="@+id/equalizer_enabled"
|
android:id="@+id/equalizer_enabled"
|
||||||
a:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
a:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
a:text="@string/equalizer.enabled"
|
android:text="@string/equalizer.enabled"
|
||||||
a:textColor="#c0c0c0"
|
android:textColor="#c0c0c0"
|
||||||
a:textAppearance="?android:attr/textAppearanceMedium"/>
|
android:textAppearance="?android:attr/textAppearanceMedium"/>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
a:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
a:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
a:orientation="vertical"
|
android:orientation="vertical"
|
||||||
a:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
a:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
a:id="@+id/equalizer_layout"
|
android:id="@+id/equalizer_layout"
|
||||||
a:orientation="vertical"
|
android:orientation="vertical"
|
||||||
a:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
a:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
a:id="@+id/equalizer_preset"
|
android:id="@+id/equalizer_preset"
|
||||||
a:text="@string/equalizer.preset"
|
android:text="@string/equalizer.preset"
|
||||||
a:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
a:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
a:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
a:layout_marginTop="20dip"
|
android:layout_marginTop="20dip"
|
||||||
a:paddingLeft="40dip"
|
android:paddingLeft="40dip"
|
||||||
a:paddingRight="40dip"/>
|
android:paddingRight="40dip"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
|
||||||
|
a:orientation="horizontal"
|
||||||
|
a:layout_width="fill_parent"
|
||||||
|
a:layout_height="wrap_content"
|
||||||
|
a:paddingBottom="12dip"
|
||||||
|
a:paddingTop="12dip" >
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
a:id="@+id/download_toggle_list"
|
||||||
|
a:layout_width="0dip"
|
||||||
|
a:layout_height="wrap_content"
|
||||||
|
a:layout_weight="1"
|
||||||
|
a:focusable="true"
|
||||||
|
a:src="?attr/media_toggle" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
a:id="@+id/download_shuffle"
|
||||||
|
a:layout_width="0dip"
|
||||||
|
a:layout_height="wrap_content"
|
||||||
|
a:layout_weight="1"
|
||||||
|
a:focusable="true"
|
||||||
|
a:src="?attr/media_shuffle" />
|
||||||
|
|
||||||
|
<com.thejoshwa.ultrasonic.androidapp.view.AutoRepeatButton
|
||||||
|
a:id="@+id/download_previous"
|
||||||
|
a:layout_width="0dip"
|
||||||
|
a:layout_height="wrap_content"
|
||||||
|
a:layout_weight="1"
|
||||||
|
a:focusable="true"
|
||||||
|
a:src="?attr/media_previous" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
a:id="@+id/download_start"
|
||||||
|
a:layout_width="0dip"
|
||||||
|
a:layout_height="wrap_content"
|
||||||
|
a:layout_weight="1"
|
||||||
|
a:focusable="true"
|
||||||
|
a:src="?attr/media_play" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
a:id="@+id/download_pause"
|
||||||
|
a:layout_width="0dip"
|
||||||
|
a:layout_height="wrap_content"
|
||||||
|
a:layout_weight="1"
|
||||||
|
a:focusable="true"
|
||||||
|
a:src="?attr/media_pause" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
a:id="@+id/download_stop"
|
||||||
|
a:layout_width="0dip"
|
||||||
|
a:layout_height="wrap_content"
|
||||||
|
a:layout_weight="1"
|
||||||
|
a:focusable="true"
|
||||||
|
a:src="?attr/media_stop" />
|
||||||
|
|
||||||
|
<com.thejoshwa.ultrasonic.androidapp.view.AutoRepeatButton
|
||||||
|
a:id="@+id/download_next"
|
||||||
|
a:layout_width="0dip"
|
||||||
|
a:layout_height="wrap_content"
|
||||||
|
a:layout_weight="1"
|
||||||
|
a:focusable="true"
|
||||||
|
a:src="?attr/media_next" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
a:id="@+id/download_repeat"
|
||||||
|
a:layout_width="0dip"
|
||||||
|
a:layout_height="wrap_content"
|
||||||
|
a:layout_weight="1"
|
||||||
|
a:focusable="true"
|
||||||
|
a:src="?attr/media_repeat_off" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
a:id="@+id/download_star"
|
||||||
|
a:layout_width="0dip"
|
||||||
|
a:layout_height="wrap_content"
|
||||||
|
a:layout_weight="1"
|
||||||
|
a:focusable="true"
|
||||||
|
a:src="?attr/star_hollow" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -4,15 +4,24 @@
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:background="@drawable/border"
|
android:orientation="vertical"
|
||||||
android:orientation="horizontal"
|
|
||||||
android:visibility="gone" >
|
android:visibility="gone" >
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_height="4dip"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:background="@drawable/drop_shadow" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/now_playing_view"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/now_playing_image"
|
android:id="@+id/now_playing_image"
|
||||||
android:layout_width="64.0dip"
|
android:layout_width="64.0dip"
|
||||||
android:layout_height="64.0dip"
|
android:layout_height="64.0dip"
|
||||||
android:layout_weight="0.0"
|
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:gravity="center" />
|
android:gravity="center" />
|
||||||
|
|
||||||
|
@ -56,4 +65,6 @@
|
||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:src="?attr/media_pause" />
|
android:src="?attr/media_pause" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
|
@ -1,10 +1,18 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<TextView xmlns:a="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
a:id="@android:id/text1"
|
android:orientation="horizontal"
|
||||||
a:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
a:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
a:textAppearance="?android:attr/textAppearanceMedium"
|
|
||||||
a:gravity="center_vertical"
|
<TextView
|
||||||
a:paddingLeft="6dip"
|
android:id="@+id/playlist_name"
|
||||||
a:paddingRight="6dip"
|
android:layout_width="0dip"
|
||||||
a:minHeight="50dip"/>
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:gravity="left|center_vertical"
|
||||||
|
android:paddingLeft="6dip"
|
||||||
|
android:paddingRight="6dip"
|
||||||
|
android:minHeight="50dip"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,70 @@
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/get_playlist_name_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:textSize="20dp"
|
||||||
|
android:text="@string/common.name" />
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/get_playlist_name"
|
||||||
|
android:inputType="text"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:hint="@string/common.name" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/get_playlist_comment_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:textSize="20dp"
|
||||||
|
android:text="@string/common.comment" />
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/get_playlist_comment"
|
||||||
|
android:inputType="text"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:hint="@string/common.comment" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/get_playlist_public_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:textSize="20dp"
|
||||||
|
android:text="@string/common.public" />
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/get_playlist_public"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginLeft="4dp"
|
||||||
|
android:checked="false"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/playlist_info"
|
||||||
|
android:title="@string/common.info"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/playlist_menu_play_now"
|
||||||
|
android:title="@string/common.play_now"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/playlist_menu_play_shuffled"
|
||||||
|
android:title="@string/common.play_shuffled"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/playlist_menu_pin"
|
||||||
|
android:title="@string/common.pin"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/playlist_update_info"
|
||||||
|
android:title="@string/playlist.update_info"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/playlist_menu_delete"
|
||||||
|
android:title="@string/common.delete"/>
|
||||||
|
|
||||||
|
</menu>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/playlist_menu_play_now"
|
||||||
|
android:title="@string/common.play_now"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/playlist_menu_play_shuffled"
|
||||||
|
android:title="@string/common.play_shuffled"/>
|
||||||
|
|
||||||
|
</menu>
|
|
@ -12,6 +12,12 @@
|
||||||
<string name="common.delete">Delete</string>
|
<string name="common.delete">Delete</string>
|
||||||
<string name="common.various_artists">Various Artists</string>
|
<string name="common.various_artists">Various Artists</string>
|
||||||
<string name="common.multiple_genres">Multiple Genres</string>
|
<string name="common.multiple_genres">Multiple Genres</string>
|
||||||
|
<string name="common.info">Details</string>
|
||||||
|
<string name="common.play_shuffled">Play Shuffled</string>
|
||||||
|
<string name="common.confirm">Confirm</string>
|
||||||
|
<string name="common.name">Name</string>
|
||||||
|
<string name="common.comment">Comment</string>
|
||||||
|
<string name="common.public">Public</string>
|
||||||
<string name="button_bar.home">UltraSonic Main</string>
|
<string name="button_bar.home">UltraSonic Main</string>
|
||||||
<string name="button_bar.browse">Media Library</string>
|
<string name="button_bar.browse">Media Library</string>
|
||||||
<string name="button_bar.search">Search</string>
|
<string name="button_bar.search">Search</string>
|
||||||
|
@ -48,7 +54,14 @@
|
||||||
<string name="menu.search">Search</string>
|
<string name="menu.search">Search</string>
|
||||||
<string name="menu.navigation">Navigation</string>
|
<string name="menu.navigation">Navigation</string>
|
||||||
<string name="menu.common">Common</string>
|
<string name="menu.common">Common</string>
|
||||||
|
<string name="menu.add_playlist">Add To Playlist</string>
|
||||||
|
<string name="menu.remove_playlist">Remove From Playlist</string>
|
||||||
|
<string name="menu.deleted_playlist">Deleted playlist %s</string>
|
||||||
|
<string name="menu.deleted_playlist_error">Failed to delete playlist %s</string>
|
||||||
<string name="playlist.label">Playlists</string>
|
<string name="playlist.label">Playlists</string>
|
||||||
|
<string name="playlist.update_info">Update Information</string>
|
||||||
|
<string name="playlist.updated_info">Updated playlist information for %s</string>
|
||||||
|
<string name="playlist.updated_info_error">Failed to update playlist information for %s</string>
|
||||||
<string name="help.label">Help</string>
|
<string name="help.label">Help</string>
|
||||||
<string name="help.title">Welcome to UltraSonic</string>
|
<string name="help.title">Welcome to UltraSonic</string>
|
||||||
<string name="help.back">Back</string>
|
<string name="help.back">Back</string>
|
||||||
|
@ -118,6 +131,11 @@
|
||||||
<string name="download.jukebox_server_too_old">Remote control is not supported. Please upgrade your Subsonic server.</string>
|
<string name="download.jukebox_server_too_old">Remote control is not supported. Please upgrade your Subsonic server.</string>
|
||||||
<string name="download.jukebox_offline">Remote control is not available in offline mode.</string>
|
<string name="download.jukebox_offline">Remote control is not available in offline mode.</string>
|
||||||
<string name="download.jukebox_not_authorized">Remote control is not allowed. Please enable jukebox mode in <b>Users > Settings</b> on your Subsonic server.</string>
|
<string name="download.jukebox_not_authorized">Remote control is not allowed. Please enable jukebox mode in <b>Users > Settings</b> on your Subsonic server.</string>
|
||||||
|
<string name="playlist_error">Failed to grab list of playlists</string>
|
||||||
|
<string name="updated_playlist">Added %1$s songs to \"%2$s\"</string>
|
||||||
|
<string name="updated_playlist_error">Failed to update \"%s\", please try later.</string>
|
||||||
|
<string name="removed_playlist">Removed %1$s songs from \"%2$s\"</string>
|
||||||
|
<string name="delete_playlist">Do you want to delete %1$s</string>
|
||||||
<string name="song_details.all">%1$s%2$s</string>
|
<string name="song_details.all">%1$s%2$s</string>
|
||||||
<string name="song_details.kbps">%d kbps</string>
|
<string name="song_details.kbps">%d kbps</string>
|
||||||
<string name="lyrics.nomatch">No lyrics found</string>
|
<string name="lyrics.nomatch">No lyrics found</string>
|
||||||
|
@ -231,6 +249,10 @@
|
||||||
<string name="settings.show_lockscreen_controls_summary">Show playback controls on the lock screen</string>
|
<string name="settings.show_lockscreen_controls_summary">Show playback controls on the lock screen</string>
|
||||||
<string name="settings.use_stream_proxy">Use Stream Proxy</string>
|
<string name="settings.use_stream_proxy">Use Stream Proxy</string>
|
||||||
<string name="settings.use_stream_proxy_summary">Stream media playback through a proxy (may help stutter)</string>
|
<string name="settings.use_stream_proxy_summary">Stream media playback through a proxy (may help stutter)</string>
|
||||||
|
<string name="settings.download_transition">Show Downloads On Play</string>
|
||||||
|
<string name="settings.download_transition_summary">Transition to download activity when starting playback</string>
|
||||||
|
<string name="settings.gapless_playback">Gapless Playback</string>
|
||||||
|
<string name="settings.gapless_playback_summary">Enable gapless playback</string>
|
||||||
<string name="settings.show_now_playing">Show Now Playing</string>
|
<string name="settings.show_now_playing">Show Now Playing</string>
|
||||||
<string name="settings.show_now_playing_summary">Show currently playing track in all activities</string>
|
<string name="settings.show_now_playing_summary">Show currently playing track in all activities</string>
|
||||||
<string name="settings.max_albums">Max Albums</string>
|
<string name="settings.max_albums">Max Albums</string>
|
||||||
|
@ -253,6 +275,12 @@
|
||||||
<string name="settings.default_artists">Default Artists</string>
|
<string name="settings.default_artists">Default Artists</string>
|
||||||
<string name="settings.default_albums">Default Albums</string>
|
<string name="settings.default_albums">Default Albums</string>
|
||||||
<string name="settings.default_songs">Default Songs</string>
|
<string name="settings.default_songs">Default Songs</string>
|
||||||
|
<string name="shuffle.startYear">Start Year:</string>
|
||||||
|
<string name="shuffle.endYear">End Year:</string>
|
||||||
|
<string name="shuffle.genre">Genre:</string>
|
||||||
|
<string name="playlist_error">Failed to grab list of playlists</string>
|
||||||
|
<string name="updated_playlist">Added %1$s songs to \"%2$s\"</string>
|
||||||
|
<string name="updated_playlist_error">Failed to update \"%s\", please try later.</string>
|
||||||
<string name="music_service.retry">A network error occurred. Retrying %1$d of %2$d.</string>
|
<string name="music_service.retry">A network error occurred. Retrying %1$d of %2$d.</string>
|
||||||
<string name="background_task.wait">Please wait…</string>
|
<string name="background_task.wait">Please wait…</string>
|
||||||
<string name="background_task.loading">Loading.</string>
|
<string name="background_task.loading">Loading.</string>
|
||||||
|
|
|
@ -159,6 +159,16 @@
|
||||||
a:key="useStreamProxy"
|
a:key="useStreamProxy"
|
||||||
a:summary="@string/settings.use_stream_proxy_summary"
|
a:summary="@string/settings.use_stream_proxy_summary"
|
||||||
a:title="@string/settings.use_stream_proxy" />
|
a:title="@string/settings.use_stream_proxy" />
|
||||||
|
<CheckBoxPreference
|
||||||
|
a:defaultValue="true"
|
||||||
|
a:key="transitionToDownloadOnPlay"
|
||||||
|
a:summary="@string/settings.download_transition_summary"
|
||||||
|
a:title="@string/settings.download_transition" />
|
||||||
|
<CheckBoxPreference
|
||||||
|
a:defaultValue="false"
|
||||||
|
a:key="gaplessPlayback"
|
||||||
|
a:summary="@string/settings.gapless_playback_summary"
|
||||||
|
a:title="@string/settings.gapless_playback" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory a:title="@string/settings.other_title" >
|
<PreferenceCategory a:title="@string/settings.other_title" >
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
|
|
|
@ -51,7 +51,6 @@ import android.view.animation.AnimationUtils;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
@ -69,8 +68,9 @@ import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
|
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.SilentBackgroundTask;
|
import com.thejoshwa.ultrasonic.androidapp.util.SilentBackgroundTask;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.SongView;
|
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.view.AutoRepeatButton;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.view.SongView;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.view.VisualizerView;
|
import com.thejoshwa.ultrasonic.androidapp.view.VisualizerView;
|
||||||
|
|
||||||
import static com.thejoshwa.ultrasonic.androidapp.domain.PlayerState.*;
|
import static com.thejoshwa.ultrasonic.androidapp.domain.PlayerState.*;
|
||||||
|
@ -78,10 +78,10 @@ import static com.thejoshwa.ultrasonic.androidapp.domain.PlayerState.*;
|
||||||
public class DownloadActivity extends SubsonicTabActivity implements OnGestureListener {
|
public class DownloadActivity extends SubsonicTabActivity implements OnGestureListener {
|
||||||
private static final String TAG = DownloadActivity.class.getSimpleName();
|
private static final String TAG = DownloadActivity.class.getSimpleName();
|
||||||
private static final int DIALOG_SAVE_PLAYLIST = 100;
|
private static final int DIALOG_SAVE_PLAYLIST = 100;
|
||||||
|
private static final int INCREMENT_TIME = 5000;
|
||||||
private static final int PERCENTAGE_OF_SCREEN_FOR_SWIPE = 5;
|
private static final int PERCENTAGE_OF_SCREEN_FOR_SWIPE = 5;
|
||||||
|
|
||||||
private ViewFlipper playlistFlipper;
|
private ViewFlipper playlistFlipper;
|
||||||
private ViewFlipper buttonBarFlipper;
|
|
||||||
private TextView emptyTextView;
|
private TextView emptyTextView;
|
||||||
private TextView songTitleTextView;
|
private TextView songTitleTextView;
|
||||||
private TextView albumTextView;
|
private TextView albumTextView;
|
||||||
|
@ -92,8 +92,8 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
private TextView durationTextView;
|
private TextView durationTextView;
|
||||||
private TextView statusTextView;
|
private TextView statusTextView;
|
||||||
private static SeekBar progressBar;
|
private static SeekBar progressBar;
|
||||||
private View previousButton;
|
private AutoRepeatButton previousButton;
|
||||||
private View nextButton;
|
private AutoRepeatButton nextButton;
|
||||||
private View pauseButton;
|
private View pauseButton;
|
||||||
private View stopButton;
|
private View stopButton;
|
||||||
private View startButton;
|
private View startButton;
|
||||||
|
@ -112,8 +112,11 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
private int swipeDistance;
|
private int swipeDistance;
|
||||||
private int swipeVelocity;
|
private int swipeVelocity;
|
||||||
private VisualizerView visualizerView;
|
private VisualizerView visualizerView;
|
||||||
|
private boolean nowPlaying = true;
|
||||||
private boolean visualizerAvailable;
|
private boolean visualizerAvailable;
|
||||||
private boolean equalizerAvailable;
|
private boolean equalizerAvailable;
|
||||||
|
private SilentBackgroundTask<Void> onProgressChangedTask;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the activity is first created.
|
* Called when the activity is first created.
|
||||||
*/
|
*/
|
||||||
|
@ -129,7 +132,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
gestureScanner = new GestureDetector(this);
|
gestureScanner = new GestureDetector(this);
|
||||||
|
|
||||||
playlistFlipper = (ViewFlipper) findViewById(R.id.download_playlist_flipper);
|
playlistFlipper = (ViewFlipper) findViewById(R.id.download_playlist_flipper);
|
||||||
buttonBarFlipper = (ViewFlipper) findViewById(R.id.download_button_bar_flipper);
|
|
||||||
emptyTextView = (TextView) findViewById(R.id.download_empty);
|
emptyTextView = (TextView) findViewById(R.id.download_empty);
|
||||||
songTitleTextView = (TextView) findViewById(R.id.download_song_title);
|
songTitleTextView = (TextView) findViewById(R.id.download_song_title);
|
||||||
albumTextView = (TextView) findViewById(R.id.download_album);
|
albumTextView = (TextView) findViewById(R.id.download_album);
|
||||||
|
@ -140,8 +142,8 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
statusTextView = (TextView) findViewById(R.id.download_status);
|
statusTextView = (TextView) findViewById(R.id.download_status);
|
||||||
progressBar = (SeekBar) findViewById(R.id.download_progress_bar);
|
progressBar = (SeekBar) findViewById(R.id.download_progress_bar);
|
||||||
playlistView = (ListView) findViewById(R.id.download_list);
|
playlistView = (ListView) findViewById(R.id.download_list);
|
||||||
previousButton = findViewById(R.id.download_previous);
|
previousButton = (AutoRepeatButton)findViewById(R.id.download_previous);
|
||||||
nextButton = findViewById(R.id.download_next);
|
nextButton = (AutoRepeatButton)findViewById(R.id.download_next);
|
||||||
pauseButton = findViewById(R.id.download_pause);
|
pauseButton = findViewById(R.id.download_pause);
|
||||||
stopButton = findViewById(R.id.download_stop);
|
stopButton = findViewById(R.id.download_stop);
|
||||||
startButton = findViewById(R.id.download_start);
|
startButton = findViewById(R.id.download_start);
|
||||||
|
@ -152,21 +154,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
|
|
||||||
toggleListButton = findViewById(R.id.download_toggle_list);
|
toggleListButton = findViewById(R.id.download_toggle_list);
|
||||||
|
|
||||||
View.OnTouchListener touchListener = new View.OnTouchListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(View v, MotionEvent me) {
|
|
||||||
return gestureScanner.onTouchEvent(me);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
previousButton.setOnTouchListener(touchListener);
|
|
||||||
nextButton.setOnTouchListener(touchListener);
|
|
||||||
pauseButton.setOnTouchListener(touchListener);
|
|
||||||
stopButton.setOnTouchListener(touchListener);
|
|
||||||
startButton.setOnTouchListener(touchListener);
|
|
||||||
buttonBarFlipper.setOnTouchListener(touchListener);
|
|
||||||
emptyTextView.setOnTouchListener(touchListener);
|
|
||||||
albumArtImageView.setOnTouchListener(touchListener);
|
|
||||||
|
|
||||||
albumArtImageView.setOnClickListener(new View.OnClickListener() {
|
albumArtImageView.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
|
@ -178,50 +165,119 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
|
|
||||||
|
new SilentBackgroundTask<Void>(DownloadActivity.this) {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Throwable {
|
||||||
getDownloadService().previous();
|
getDownloadService().previous();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(Void result) {
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onSliderProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
previousButton.setOnRepeatListener(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
changeProgress(-INCREMENT_TIME);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
nextButton.setOnClickListener(new View.OnClickListener() {
|
nextButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
|
|
||||||
|
new SilentBackgroundTask<Boolean>(DownloadActivity.this) {
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground() throws Throwable {
|
||||||
if (getDownloadService().getCurrentPlayingIndex() < getDownloadService().size() - 1) {
|
if (getDownloadService().getCurrentPlayingIndex() < getDownloadService().size() - 1) {
|
||||||
getDownloadService().next();
|
getDownloadService().next();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(Boolean result) {
|
||||||
|
if(result) {
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onSliderProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
nextButton.setOnRepeatListener(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
changeProgress(INCREMENT_TIME);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pauseButton.setOnClickListener(new View.OnClickListener() {
|
pauseButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
|
new SilentBackgroundTask<Void>(DownloadActivity.this) {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Throwable {
|
||||||
getDownloadService().pause();
|
getDownloadService().pause();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(Void result) {
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onSliderProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
stopButton.setOnClickListener(new View.OnClickListener() {
|
stopButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
|
new SilentBackgroundTask<Void>(DownloadActivity.this) {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Throwable {
|
||||||
getDownloadService().reset();
|
getDownloadService().reset();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(Void result) {
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onSliderProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
startButton.setOnClickListener(new View.OnClickListener() {
|
startButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
|
|
||||||
|
new SilentBackgroundTask<Void>(DownloadActivity.this) {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Throwable {
|
||||||
start();
|
start();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(Void result) {
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onSliderProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
shuffleButton.setOnClickListener(new View.OnClickListener() {
|
shuffleButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@ -265,9 +321,20 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
|
new SilentBackgroundTask<Void>(DownloadActivity.this) {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Throwable {
|
||||||
getDownloadService().seekTo(getProgressBar().getProgress());
|
getDownloadService().seekTo(getProgressBar().getProgress());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(Void result) {
|
||||||
onSliderProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
|
}.execute();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
@ -280,12 +347,23 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
|
|
||||||
playlistView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
playlistView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
|
|
||||||
|
new SilentBackgroundTask<Void>(DownloadActivity.this) {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Throwable {
|
||||||
getDownloadService().play(position);
|
getDownloadService().play(position);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(Void result) {
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onSliderProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerForContextMenu(playlistView);
|
registerForContextMenu(playlistView);
|
||||||
|
@ -381,7 +459,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
DownloadService downloadService = getDownloadService();
|
DownloadService downloadService = getDownloadService();
|
||||||
if (downloadService == null || downloadService.getCurrentPlaying() == null) {
|
if (downloadService == null || downloadService.getCurrentPlaying() == null) {
|
||||||
playlistFlipper.setDisplayedChild(1);
|
playlistFlipper.setDisplayedChild(1);
|
||||||
buttonBarFlipper.setDisplayedChild(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDownloadListChanged();
|
onDownloadListChanged();
|
||||||
|
@ -465,7 +542,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
@Override
|
@Override
|
||||||
protected void onPrepareDialog(int id, Dialog dialog) {
|
protected void onPrepareDialog(int id, Dialog dialog) {
|
||||||
if (id == DIALOG_SAVE_PLAYLIST) {
|
if (id == DIALOG_SAVE_PLAYLIST) {
|
||||||
String playlistName = getDownloadService().getSuggestedPlaylistName();
|
String playlistName = (getDownloadService() != null) ? getDownloadService().getSuggestedPlaylistName() : null;
|
||||||
if (playlistName != null) {
|
if (playlistName != null) {
|
||||||
playlistNameView.setText(playlistName);
|
playlistNameView.setText(playlistName);
|
||||||
} else {
|
} else {
|
||||||
|
@ -623,7 +700,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground() throws Throwable {
|
protected Void doInBackground() throws Throwable {
|
||||||
List<MusicDirectory.Entry> entries = new LinkedList<MusicDirectory.Entry>();
|
List<MusicDirectory.Entry> entries = new LinkedList<MusicDirectory.Entry>();
|
||||||
for (DownloadFile downloadFile : getDownloadService().getDownloads()) {
|
for (DownloadFile downloadFile : getDownloadService().getSongs()) {
|
||||||
entries.add(downloadFile.getSong());
|
entries.add(downloadFile.getSong());
|
||||||
}
|
}
|
||||||
MusicService musicService = MusicServiceFactory.getMusicService(DownloadActivity.this);
|
MusicService musicService = MusicServiceFactory.getMusicService(DownloadActivity.this);
|
||||||
|
@ -650,25 +727,17 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
playlistFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_down_in));
|
playlistFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_down_in));
|
||||||
playlistFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_down_out));
|
playlistFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_down_out));
|
||||||
playlistFlipper.setDisplayedChild(0);
|
playlistFlipper.setDisplayedChild(0);
|
||||||
buttonBarFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_down_in));
|
|
||||||
buttonBarFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_down_out));
|
|
||||||
buttonBarFlipper.setDisplayedChild(0);
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
playlistFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_in));
|
playlistFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_in));
|
||||||
playlistFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_out));
|
playlistFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_out));
|
||||||
playlistFlipper.setDisplayedChild(1);
|
playlistFlipper.setDisplayedChild(1);
|
||||||
buttonBarFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_in));
|
|
||||||
buttonBarFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_out));
|
|
||||||
buttonBarFlipper.setDisplayedChild(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void start() {
|
private void start() {
|
||||||
DownloadService service = getDownloadService();
|
DownloadService service = getDownloadService();
|
||||||
PlayerState state = service.getPlayerState();
|
PlayerState state = service.getPlayerState();
|
||||||
if (state == PAUSED || state == COMPLETED) {
|
if (state == PAUSED || state == COMPLETED || state == STOPPED) {
|
||||||
service.start();
|
service.start();
|
||||||
} else if (state == STOPPED || state == IDLE) {
|
} else if (state == STOPPED || state == IDLE) {
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
|
@ -683,13 +752,24 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDownloadListChanged() {
|
private void onDownloadListChanged() {
|
||||||
|
onDownloadListChanged(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDownloadListChanged(boolean refresh) {
|
||||||
DownloadService downloadService = getDownloadService();
|
DownloadService downloadService = getDownloadService();
|
||||||
if (downloadService == null) {
|
if (downloadService == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DownloadFile> list = downloadService.getDownloads();
|
List<DownloadFile> list;
|
||||||
|
if(nowPlaying) {
|
||||||
|
list = downloadService.getSongs();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
list = downloadService.getBackgroundDownloads();
|
||||||
|
}
|
||||||
|
|
||||||
|
emptyTextView.setText(R.string.download_empty);
|
||||||
playlistView.setAdapter(new SongListAdapter(list));
|
playlistView.setAdapter(new SongListAdapter(list));
|
||||||
emptyTextView.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE);
|
emptyTextView.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE);
|
||||||
currentRevision = downloadService.getDownloadListUpdateRevision();
|
currentRevision = downloadService.getDownloadListUpdateRevision();
|
||||||
|
@ -733,20 +813,37 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSliderProgressChanged() {
|
private void onSliderProgressChanged() {
|
||||||
if (getDownloadService() == null) {
|
if (getDownloadService() == null || onProgressChangedTask != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onProgressChangedTask = new SilentBackgroundTask<Void>(this) {
|
||||||
|
DownloadService downloadService;
|
||||||
|
boolean isJukeboxEnabled;
|
||||||
|
int millisPlayed;
|
||||||
|
Integer duration;
|
||||||
|
PlayerState playerState;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Throwable {
|
||||||
|
downloadService = getDownloadService();
|
||||||
|
isJukeboxEnabled = downloadService.isJukeboxEnabled();
|
||||||
|
millisPlayed = Math.max(0, downloadService.getPlayerPosition());
|
||||||
|
duration = downloadService.getPlayerDuration();
|
||||||
|
playerState = getDownloadService().getPlayerState();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(Void result) {
|
||||||
if (currentPlaying != null) {
|
if (currentPlaying != null) {
|
||||||
int millisPlayed = Math.max(0, getDownloadService().getPlayerPosition());
|
|
||||||
Integer duration = getDownloadService().getPlayerDuration();
|
|
||||||
int millisTotal = duration == null ? 0 : duration;
|
int millisTotal = duration == null ? 0 : duration;
|
||||||
|
|
||||||
positionTextView.setText(Util.formatDuration(millisPlayed / 1000));
|
positionTextView.setText(Util.formatDuration(millisPlayed / 1000));
|
||||||
durationTextView.setText(Util.formatDuration(millisTotal / 1000));
|
durationTextView.setText(Util.formatDuration(millisTotal / 1000));
|
||||||
progressBar.setMax(millisTotal == 0 ? 100 : millisTotal); // Work-around for apparent bug.
|
progressBar.setMax(millisTotal == 0 ? 100 : millisTotal); // Work-around for apparent bug.
|
||||||
progressBar.setProgress(millisPlayed);
|
progressBar.setProgress(millisPlayed);
|
||||||
progressBar.setEnabled(currentPlaying.isCompleteFileAvailable() || getDownloadService().isJukeboxEnabled());
|
progressBar.setEnabled(currentPlaying.isWorkDone() || isJukeboxEnabled);
|
||||||
} else {
|
} else {
|
||||||
positionTextView.setText(R.string.util_zero_time);
|
positionTextView.setText(R.string.util_zero_time);
|
||||||
durationTextView.setText(R.string.util_no_time);
|
durationTextView.setText(R.string.util_no_time);
|
||||||
|
@ -755,19 +852,22 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
progressBar.setEnabled(false);
|
progressBar.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerState playerState = getDownloadService().getPlayerState();
|
|
||||||
|
|
||||||
switch (playerState) {
|
switch (playerState) {
|
||||||
case DOWNLOADING:
|
case DOWNLOADING:
|
||||||
long bytes = currentPlaying.getPartialFile().length();
|
long bytes = currentPlaying.getPartialFile().length();
|
||||||
statusTextView.setText(getResources().getString(R.string.download_playerstate_downloading, Util.formatLocalizedBytes(bytes, this)));
|
statusTextView.setText(getResources().getString(
|
||||||
|
R.string.download_playerstate_downloading,
|
||||||
|
Util.formatLocalizedBytes(bytes,
|
||||||
|
DownloadActivity.this)));
|
||||||
break;
|
break;
|
||||||
case PREPARING:
|
case PREPARING:
|
||||||
statusTextView.setText(R.string.download_playerstate_buffering);
|
statusTextView
|
||||||
|
.setText(R.string.download_playerstate_buffering);
|
||||||
break;
|
break;
|
||||||
case STARTED:
|
case STARTED:
|
||||||
if (getDownloadService().isShufflePlayEnabled()) {
|
if (getDownloadService().isShufflePlayEnabled()) {
|
||||||
statusTextView.setText(R.string.download_playerstate_playing_shuffle);
|
statusTextView
|
||||||
|
.setText(R.string.download_playerstate_playing_shuffle);
|
||||||
} else {
|
} else {
|
||||||
statusTextView.setText(null);
|
statusTextView.setText(null);
|
||||||
}
|
}
|
||||||
|
@ -795,6 +895,44 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
startButton.setVisibility(View.VISIBLE);
|
startButton.setVisibility(View.VISIBLE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onProgressChangedTask = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onProgressChangedTask.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeProgress(final int ms) {
|
||||||
|
final DownloadService downloadService = getDownloadService();
|
||||||
|
if(downloadService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new SilentBackgroundTask<Void>(this) {
|
||||||
|
int msPlayed;
|
||||||
|
Integer duration;
|
||||||
|
int seekTo;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Throwable {
|
||||||
|
msPlayed = Math.max(0, downloadService.getPlayerPosition());
|
||||||
|
duration = downloadService.getPlayerDuration();
|
||||||
|
|
||||||
|
int msTotal = duration == null ? 0 : duration;
|
||||||
|
if(msPlayed + ms > msTotal) {
|
||||||
|
seekTo = msTotal;
|
||||||
|
} else {
|
||||||
|
seekTo = msPlayed + ms;
|
||||||
|
}
|
||||||
|
downloadService.seekTo(seekTo);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(Void result) {
|
||||||
|
progressBar.setProgress(seekTo);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SongListAdapter extends ArrayAdapter<DownloadFile> {
|
private class SongListAdapter extends ArrayAdapter<DownloadFile> {
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
package com.thejoshwa.ultrasonic.androidapp.activity;
|
|
||||||
|
|
||||||
public class GenericActivity {
|
|
||||||
|
|
||||||
}
|
|
|
@ -366,12 +366,6 @@ public class MainActivity extends SubsonicTabActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restart() {
|
|
||||||
Intent intent = new Intent(this, MainActivity.class);
|
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
||||||
Util.startActivityWithoutTransition(this, intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void exit() {
|
private void exit() {
|
||||||
stopService(new Intent(this, DownloadServiceImpl.class));
|
stopService(new Intent(this, DownloadServiceImpl.class));
|
||||||
Util.unregisterMediaButtonEventReceiver(this);
|
Util.unregisterMediaButtonEventReceiver(this);
|
||||||
|
|
|
@ -90,7 +90,8 @@ public final class PlayVideoActivity extends Activity {
|
||||||
|
|
||||||
private String getVideoUrl() {
|
private String getVideoUrl() {
|
||||||
String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID);
|
String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID);
|
||||||
return MusicServiceFactory.getMusicService(this).getVideoUrl(this, id);
|
int maxBitrate = Util.getMaxVideoBitrate(this);
|
||||||
|
return MusicServiceFactory.getMusicService(this).getVideoUrl(maxBitrate, this, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -44,13 +44,13 @@ import com.thejoshwa.ultrasonic.androidapp.domain.SearchResult;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadService;
|
import com.thejoshwa.ultrasonic.androidapp.service.DownloadService;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.ArtistAdapter;
|
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.BackgroundTask;
|
import com.thejoshwa.ultrasonic.androidapp.util.BackgroundTask;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
|
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.EntryAdapter;
|
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.MergeAdapter;
|
import com.thejoshwa.ultrasonic.androidapp.util.MergeAdapter;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.TabActivityBackgroundTask;
|
import com.thejoshwa.ultrasonic.androidapp.util.TabActivityBackgroundTask;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.view.ArtistAdapter;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.view.EntryAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs searches and displays the matching artists, albums and songs.
|
* Performs searches and displays the matching artists, albums and songs.
|
||||||
|
@ -190,16 +190,16 @@ public class SearchActivity extends SubsonicTabActivity {
|
||||||
|
|
||||||
switch (menuItem.getItemId()) {
|
switch (menuItem.getItemId()) {
|
||||||
case R.id.album_menu_play_now:
|
case R.id.album_menu_play_now:
|
||||||
downloadRecursively(id, false, false, true, false);
|
downloadRecursively(id, false, false, true, false, false);
|
||||||
break;
|
break;
|
||||||
case R.id.album_menu_play_next:
|
case R.id.album_menu_play_next:
|
||||||
downloadRecursively(id, false, true, false, true);
|
downloadRecursively(id, false, true, false, true, false);
|
||||||
break;
|
break;
|
||||||
case R.id.album_menu_play_last:
|
case R.id.album_menu_play_last:
|
||||||
downloadRecursively(id, false, true, false, false);
|
downloadRecursively(id, false, true, false, false, false);
|
||||||
break;
|
break;
|
||||||
case R.id.album_menu_pin:
|
case R.id.album_menu_pin:
|
||||||
downloadRecursively(id, true, true, false, false);
|
downloadRecursively(id, true, true, false, false, false);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return super.onContextItemSelected(menuItem);
|
return super.onContextItemSelected(menuItem);
|
||||||
|
@ -330,7 +330,7 @@ public class SearchActivity extends SubsonicTabActivity {
|
||||||
if (!append) {
|
if (!append) {
|
||||||
downloadService.clear();
|
downloadService.clear();
|
||||||
}
|
}
|
||||||
downloadService.download(Arrays.asList(song), save, false, playNext);
|
downloadService.download(Arrays.asList(song), save, false, playNext, false);
|
||||||
if (autoplay) {
|
if (autoplay) {
|
||||||
downloadService.play(downloadService.size() - 1);
|
downloadService.play(downloadService.size() - 1);
|
||||||
}
|
}
|
||||||
|
@ -340,8 +340,10 @@ public class SearchActivity extends SubsonicTabActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onVideoSelected(MusicDirectory.Entry entry) {
|
private void onVideoSelected(MusicDirectory.Entry entry) {
|
||||||
|
int maxBitrate = Util.getMaxVideoBitrate(this);
|
||||||
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
intent.setData(Uri.parse(MusicServiceFactory.getMusicService(this).getVideoUrl(this, entry.getId())));
|
intent.setData(Uri.parse(MusicServiceFactory.getMusicService(this).getVideoUrl(maxBitrate, this, entry.getId())));
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,11 +42,13 @@ import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
|
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.EntryAdapter;
|
import com.thejoshwa.ultrasonic.androidapp.util.FileUtil;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Pair;
|
import com.thejoshwa.ultrasonic.androidapp.util.Pair;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.TabActivityBackgroundTask;
|
import com.thejoshwa.ultrasonic.androidapp.util.TabActivityBackgroundTask;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.view.EntryAdapter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -142,28 +144,26 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
||||||
playNowButton.setOnClickListener(new View.OnClickListener() {
|
playNowButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
download(false, false, true, false);
|
playNow(false, false);
|
||||||
selectAll(false, false);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
playNextButton.setOnClickListener(new View.OnClickListener() {
|
playNextButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
download(true, false, false, true);
|
download(true, false, false, true, false);
|
||||||
selectAll(false, false);
|
selectAll(false, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
playLastButton.setOnClickListener(new View.OnClickListener() {
|
playLastButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
download(true, false, false, false);
|
playNow(false, true);
|
||||||
selectAll(false, false);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
pinButton.setOnClickListener(new View.OnClickListener() {
|
pinButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
download(true, true, false, false);
|
downloadBackground(true);
|
||||||
selectAll(false, false);
|
selectAll(false, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -234,7 +234,21 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void playNow(final boolean shuffle, final boolean append) {
|
||||||
|
if(getSelectedSongs().size() > 0) {
|
||||||
|
download(append, false, !append, false, shuffle);
|
||||||
|
selectAll(false, false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
playAll(shuffle, append);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void playAll() {
|
private void playAll() {
|
||||||
|
playAll(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playAll(final boolean shuffle, final boolean append) {
|
||||||
boolean hasSubFolders = false;
|
boolean hasSubFolders = false;
|
||||||
for (int i = 0; i < albumListView.getCount(); i++) {
|
for (int i = 0; i < albumListView.getCount(); i++) {
|
||||||
MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(i);
|
MusicDirectory.Entry entry = (MusicDirectory.Entry) albumListView.getItemAtPosition(i);
|
||||||
|
@ -246,10 +260,10 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
||||||
|
|
||||||
String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID);
|
String id = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID);
|
||||||
if (hasSubFolders && id != null) {
|
if (hasSubFolders && id != null) {
|
||||||
downloadRecursively(id, false, false, true, false);
|
downloadRecursively(id, false, append, !append, shuffle, false);
|
||||||
} else {
|
} else {
|
||||||
selectAll(true, false);
|
selectAll(true, false);
|
||||||
download(false, false, true, false);
|
download(append, false, !append, false, shuffle);
|
||||||
selectAll(false, false);
|
selectAll(false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,16 +296,16 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
||||||
songs.add((MusicDirectory.Entry) albumListView.getItemAtPosition(info.position));
|
songs.add((MusicDirectory.Entry) albumListView.getItemAtPosition(info.position));
|
||||||
switch (menuItem.getItemId()) {
|
switch (menuItem.getItemId()) {
|
||||||
case R.id.album_menu_play_now:
|
case R.id.album_menu_play_now:
|
||||||
downloadRecursively(entry.getId(), false, false, true, false);
|
downloadRecursively(entry.getId(), false, false, true, false, false);
|
||||||
break;
|
break;
|
||||||
case R.id.album_menu_play_next:
|
case R.id.album_menu_play_next:
|
||||||
downloadRecursively(entry.getId(), false, false, true, true);
|
downloadRecursively(entry.getId(), false, false, true, true, false);
|
||||||
break;
|
break;
|
||||||
case R.id.album_menu_play_last:
|
case R.id.album_menu_play_last:
|
||||||
downloadRecursively(entry.getId(), false, true, false, false);
|
downloadRecursively(entry.getId(), false, true, false, false, false);
|
||||||
break;
|
break;
|
||||||
case R.id.album_menu_pin:
|
case R.id.album_menu_pin:
|
||||||
downloadRecursively(entry.getId(), true, true, false, false);
|
downloadRecursively(entry.getId(), true, true, false, false, false);
|
||||||
break;
|
break;
|
||||||
case R.id.select_album_play_all:
|
case R.id.select_album_play_all:
|
||||||
playAll();
|
playAll();
|
||||||
|
@ -321,14 +335,14 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getMusicDirectory(final String id, String name) {
|
private void getMusicDirectory(final String id, final String name) {
|
||||||
getActionBar().setSubtitle(name);
|
getActionBar().setSubtitle(name);
|
||||||
|
|
||||||
new LoadTask() {
|
new LoadTask() {
|
||||||
@Override
|
@Override
|
||||||
protected MusicDirectory load(MusicService service) throws Exception {
|
protected MusicDirectory load(MusicService service) throws Exception {
|
||||||
boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false);
|
boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false);
|
||||||
return service.getMusicDirectory(id, refresh, SelectAlbumActivity.this, this);
|
return service.getMusicDirectory(id, name, refresh, SelectAlbumActivity.this, this);
|
||||||
}
|
}
|
||||||
}.execute();
|
}.execute();
|
||||||
}
|
}
|
||||||
|
@ -393,13 +407,13 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
||||||
}.execute();
|
}.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getPlaylist(final String playlistId, String playlistName) {
|
private void getPlaylist(final String playlistId, final String playlistName) {
|
||||||
getActionBar().setSubtitle(playlistName);
|
getActionBar().setSubtitle(playlistName);
|
||||||
|
|
||||||
new LoadTask() {
|
new LoadTask() {
|
||||||
@Override
|
@Override
|
||||||
protected MusicDirectory load(MusicService service) throws Exception {
|
protected MusicDirectory load(MusicService service) throws Exception {
|
||||||
return service.getPlaylist(playlistId, SelectAlbumActivity.this, this);
|
return service.getPlaylist(playlistId, playlistName, SelectAlbumActivity.this, this);
|
||||||
}
|
}
|
||||||
}.execute();
|
}.execute();
|
||||||
}
|
}
|
||||||
|
@ -527,7 +541,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
||||||
return songs;
|
return songs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void download(final boolean append, final boolean save, final boolean autoplay, final boolean playNext) {
|
private void download(final boolean append, final boolean save, final boolean autoplay, final boolean playNext, final boolean shuffle) {
|
||||||
if (getDownloadService() == null) {
|
if (getDownloadService() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -541,15 +555,16 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
getDownloadService().download(songs, save, autoplay, playNext);
|
getDownloadService().download(songs, save, autoplay, playNext, shuffle);
|
||||||
String playlistName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME);
|
String playlistName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME);
|
||||||
|
|
||||||
if (playlistName != null) {
|
if (playlistName != null) {
|
||||||
getDownloadService().setSuggestedPlaylistName(playlistName);
|
getDownloadService().setSuggestedPlaylistName(playlistName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (autoplay) {
|
if (autoplay) {
|
||||||
|
if (Util.getShouldTransitionOnPlaybackPreference(SelectAlbumActivity.this)) {
|
||||||
Util.startActivityWithoutTransition(SelectAlbumActivity.this, DownloadActivity.class);
|
Util.startActivityWithoutTransition(SelectAlbumActivity.this, DownloadActivity.class);
|
||||||
|
}
|
||||||
} else if (save) {
|
} else if (save) {
|
||||||
Util.toast(SelectAlbumActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_downloading, songs.size(), songs.size()));
|
Util.toast(SelectAlbumActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_downloading, songs.size(), songs.size()));
|
||||||
} else if (playNext) {
|
} else if (playNext) {
|
||||||
|
@ -563,9 +578,41 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
||||||
checkLicenseAndTrialPeriod(onValid);
|
checkLicenseAndTrialPeriod(onValid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void downloadBackground(final boolean save) {
|
||||||
|
List<MusicDirectory.Entry> songs = getSelectedSongs();
|
||||||
|
if(songs.isEmpty()) {
|
||||||
|
selectAll(true, false);
|
||||||
|
songs = getSelectedSongs();
|
||||||
|
}
|
||||||
|
downloadBackground(save, songs);
|
||||||
|
}
|
||||||
|
private void downloadBackground(final boolean save, final List<MusicDirectory.Entry> songs) {
|
||||||
|
if (getDownloadService() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Runnable onValid = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
warnIfNetworkOrStorageUnavailable();
|
||||||
|
getDownloadService().downloadBackground(songs, save);
|
||||||
|
|
||||||
|
Util.toast(SelectAlbumActivity.this,
|
||||||
|
getResources().getQuantityString(R.plurals.select_album_n_songs_downloading, songs.size(), songs.size()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkLicenseAndTrialPeriod(onValid);
|
||||||
|
}
|
||||||
|
|
||||||
private void delete() {
|
private void delete() {
|
||||||
|
List<MusicDirectory.Entry> songs = getSelectedSongs();
|
||||||
|
if(songs.isEmpty()) {
|
||||||
|
selectAll(true, false);
|
||||||
|
songs = getSelectedSongs();
|
||||||
|
}
|
||||||
if (getDownloadService() != null) {
|
if (getDownloadService() != null) {
|
||||||
getDownloadService().delete(getSelectedSongs());
|
getDownloadService().delete(songs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -578,12 +625,22 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void playVideo(MusicDirectory.Entry entry) {
|
private void playVideo(MusicDirectory.Entry entry) {
|
||||||
|
int maxBitrate = Util.getMaxVideoBitrate(this);
|
||||||
|
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
intent.setData(Uri.parse(MusicServiceFactory.getMusicService(this).getVideoUrl(this, entry.getId())));
|
intent.setData(Uri.parse(MusicServiceFactory.getMusicService(this).getVideoUrl(maxBitrate, this, entry.getId())));
|
||||||
|
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteRecursively(MusicDirectory.Entry album) {
|
||||||
|
File dir = FileUtil.getAlbumDirectory(this, album);
|
||||||
|
Util.recursiveDelete(dir);
|
||||||
|
if(Util.isOffline(this)) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void checkLicenseAndTrialPeriod(Runnable onValid) {
|
private void checkLicenseAndTrialPeriod(Runnable onValid) {
|
||||||
if (licenseValid) {
|
if (licenseValid) {
|
||||||
onValid.run();
|
onValid.run();
|
||||||
|
|
|
@ -41,11 +41,11 @@ import com.thejoshwa.ultrasonic.androidapp.domain.Indexes;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicFolder;
|
import com.thejoshwa.ultrasonic.androidapp.domain.MusicFolder;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.ArtistAdapter;
|
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.BackgroundTask;
|
import com.thejoshwa.ultrasonic.androidapp.util.BackgroundTask;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
|
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.TabActivityBackgroundTask;
|
import com.thejoshwa.ultrasonic.androidapp.util.TabActivityBackgroundTask;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.view.ArtistAdapter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -129,7 +129,7 @@ public class SelectArtistActivity extends SubsonicTabActivity implements Adapter
|
||||||
boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false);
|
boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false);
|
||||||
MusicService musicService = MusicServiceFactory.getMusicService(SelectArtistActivity.this);
|
MusicService musicService = MusicServiceFactory.getMusicService(SelectArtistActivity.this);
|
||||||
if (!Util.isOffline(SelectArtistActivity.this)) {
|
if (!Util.isOffline(SelectArtistActivity.this)) {
|
||||||
musicFolders = musicService.getMusicFolders(SelectArtistActivity.this, this);
|
musicFolders = musicService.getMusicFolders(refresh, SelectArtistActivity.this, this);
|
||||||
}
|
}
|
||||||
String musicFolderId = Util.getSelectedMusicFolderId(SelectArtistActivity.this);
|
String musicFolderId = Util.getSelectedMusicFolderId(SelectArtistActivity.this);
|
||||||
return musicService.getIndexes(musicFolderId, refresh, SelectArtistActivity.this, this);
|
return musicService.getIndexes(musicFolderId, refresh, SelectArtistActivity.this, this);
|
||||||
|
@ -214,16 +214,16 @@ public class SelectArtistActivity extends SubsonicTabActivity implements Adapter
|
||||||
if (artist != null) {
|
if (artist != null) {
|
||||||
switch (menuItem.getItemId()) {
|
switch (menuItem.getItemId()) {
|
||||||
case R.id.artist_menu_play_now:
|
case R.id.artist_menu_play_now:
|
||||||
downloadRecursively(artist.getId(), false, false, true, false);
|
downloadRecursively(artist.getId(), false, false, true, false, false);
|
||||||
break;
|
break;
|
||||||
case R.id.artist_menu_play_next:
|
case R.id.artist_menu_play_next:
|
||||||
downloadRecursively(artist.getId(), false, false, true, true);
|
downloadRecursively(artist.getId(), false, false, true, true, false);
|
||||||
break;
|
break;
|
||||||
case R.id.artist_menu_play_last:
|
case R.id.artist_menu_play_last:
|
||||||
downloadRecursively(artist.getId(), false, true, false, false);
|
downloadRecursively(artist.getId(), false, true, false, false, false);
|
||||||
break;
|
break;
|
||||||
case R.id.artist_menu_pin:
|
case R.id.artist_menu_pin:
|
||||||
downloadRecursively(artist.getId(), true, true, false, false);
|
downloadRecursively(artist.getId(), true, true, false, false, false);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return super.onContextItemSelected(menuItem);
|
return super.onContextItemSelected(menuItem);
|
||||||
|
|
|
@ -19,15 +19,20 @@
|
||||||
|
|
||||||
package com.thejoshwa.ultrasonic.androidapp.activity;
|
package com.thejoshwa.ultrasonic.androidapp.activity;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.EditText;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
import com.handmark.pulltorefresh.library.PullToRefreshBase;
|
import com.handmark.pulltorefresh.library.PullToRefreshBase;
|
||||||
|
@ -37,20 +42,24 @@ import com.thejoshwa.ultrasonic.androidapp.R;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.Playlist;
|
import com.thejoshwa.ultrasonic.androidapp.domain.Playlist;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.service.OfflineException;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.service.ServerTooOldException;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.BackgroundTask;
|
import com.thejoshwa.ultrasonic.androidapp.util.BackgroundTask;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.util.CacheCleaner;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
|
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.util.LoadingTask;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.TabActivityBackgroundTask;
|
import com.thejoshwa.ultrasonic.androidapp.util.TabActivityBackgroundTask;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.view.PlaylistAdapter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SelectPlaylistActivity extends SubsonicTabActivity implements AdapterView.OnItemClickListener {
|
public class SelectPlaylistActivity extends SubsonicTabActivity implements AdapterView.OnItemClickListener {
|
||||||
|
|
||||||
private static final int MENU_ITEM_PLAY_ALL = 1;
|
|
||||||
|
|
||||||
private PullToRefreshListView refreshPlaylistsListView;
|
private PullToRefreshListView refreshPlaylistsListView;
|
||||||
private ListView playlistsListView;
|
private ListView playlistsListView;
|
||||||
private View emptyTextView;
|
private View emptyTextView;
|
||||||
|
private PlaylistAdapter playlistAdapter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -99,12 +108,16 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
|
||||||
protected List<Playlist> doInBackground() throws Throwable {
|
protected List<Playlist> doInBackground() throws Throwable {
|
||||||
MusicService musicService = MusicServiceFactory.getMusicService(SelectPlaylistActivity.this);
|
MusicService musicService = MusicServiceFactory.getMusicService(SelectPlaylistActivity.this);
|
||||||
boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false);
|
boolean refresh = getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false);
|
||||||
return musicService.getPlaylists(refresh, SelectPlaylistActivity.this, this);
|
List<Playlist> playlists = musicService.getPlaylists(refresh, SelectPlaylistActivity.this, this);
|
||||||
|
|
||||||
|
if(!Util.isOffline(SelectPlaylistActivity.this))
|
||||||
|
new CacheCleaner(SelectPlaylistActivity.this, getDownloadService()).cleanPlaylists(playlists);
|
||||||
|
return playlists;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void done(List<Playlist> result) {
|
protected void done(List<Playlist> result) {
|
||||||
playlistsListView.setAdapter(new PlaylistAdapter(result));
|
playlistsListView.setAdapter(playlistAdapter = new PlaylistAdapter(SelectPlaylistActivity.this, result));
|
||||||
emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE);
|
emptyTextView.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -114,7 +127,12 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
|
||||||
@Override
|
@Override
|
||||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
|
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
|
||||||
super.onCreateContextMenu(menu, view, menuInfo);
|
super.onCreateContextMenu(menu, view, menuInfo);
|
||||||
menu.add(Menu.NONE, MENU_ITEM_PLAY_ALL, MENU_ITEM_PLAY_ALL, R.string.common_play_now);
|
|
||||||
|
MenuInflater inflater = getMenuInflater();
|
||||||
|
if (Util.isOffline(this))
|
||||||
|
inflater.inflate(R.menu.select_playlist_context_offline, menu);
|
||||||
|
else
|
||||||
|
inflater.inflate(R.menu.select_playlist_context, menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -122,14 +140,35 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
|
||||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
|
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
|
||||||
Playlist playlist = (Playlist) playlistsListView.getItemAtPosition(info.position);
|
Playlist playlist = (Playlist) playlistsListView.getItemAtPosition(info.position);
|
||||||
|
|
||||||
|
Intent intent;
|
||||||
switch (menuItem.getItemId()) {
|
switch (menuItem.getItemId()) {
|
||||||
case MENU_ITEM_PLAY_ALL:
|
case R.id.playlist_menu_pin:
|
||||||
Intent intent = new Intent(SelectPlaylistActivity.this, SelectAlbumActivity.class);
|
downloadPlaylist(playlist.getId(), playlist.getName(), true, true, false, false, true);
|
||||||
|
break;
|
||||||
|
case R.id.playlist_menu_play_now:
|
||||||
|
intent = new Intent(SelectPlaylistActivity.this, SelectAlbumActivity.class);
|
||||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||||
Util.startActivityWithoutTransition(SelectPlaylistActivity.this, intent);
|
Util.startActivityWithoutTransition(SelectPlaylistActivity.this, intent);
|
||||||
break;
|
break;
|
||||||
|
case R.id.playlist_menu_play_shuffled:
|
||||||
|
intent = new Intent(SelectPlaylistActivity.this, SelectAlbumActivity.class);
|
||||||
|
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID, playlist.getId());
|
||||||
|
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||||
|
intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||||
|
intent.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true);
|
||||||
|
Util.startActivityWithoutTransition(SelectPlaylistActivity.this, intent);
|
||||||
|
break;
|
||||||
|
case R.id.playlist_menu_delete:
|
||||||
|
deletePlaylist(playlist);
|
||||||
|
break;
|
||||||
|
case R.id.playlist_info:
|
||||||
|
displayPlaylistInfo(playlist);
|
||||||
|
break;
|
||||||
|
case R.id.playlist_update_info:
|
||||||
|
updatePlaylistInfo(playlist);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return super.onContextItemSelected(menuItem);
|
return super.onContextItemSelected(menuItem);
|
||||||
}
|
}
|
||||||
|
@ -158,10 +197,114 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
|
||||||
Util.startActivityWithoutTransition(SelectPlaylistActivity.this, intent);
|
Util.startActivityWithoutTransition(SelectPlaylistActivity.this, intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PlaylistAdapter extends ArrayAdapter<Playlist> {
|
private void deletePlaylist(final Playlist playlist) {
|
||||||
public PlaylistAdapter(List<Playlist> playlists) {
|
new AlertDialog.Builder(this)
|
||||||
super(SelectPlaylistActivity.this, R.layout.playlist_list_item, playlists);
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.setTitle(R.string.common_confirm)
|
||||||
|
.setMessage(getResources().getString(R.string.delete_playlist, playlist.getName()))
|
||||||
|
.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
new LoadingTask<Void>(SelectPlaylistActivity.this, false) {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Throwable {
|
||||||
|
MusicService musicService = MusicServiceFactory.getMusicService(SelectPlaylistActivity.this);
|
||||||
|
musicService.deletePlaylist(playlist.getId(), SelectPlaylistActivity.this, null);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(Void result) {
|
||||||
|
playlistAdapter.remove(playlist);
|
||||||
|
playlistAdapter.notifyDataSetChanged();
|
||||||
|
Util.toast(SelectPlaylistActivity.this, getResources().getString(R.string.menu_deleted_playlist, playlist.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void error(Throwable error) {
|
||||||
|
String msg;
|
||||||
|
if (error instanceof OfflineException || error instanceof ServerTooOldException) {
|
||||||
|
msg = getErrorMessage(error);
|
||||||
|
} else {
|
||||||
|
msg = getResources().getString(R.string.menu_deleted_playlist_error, playlist.getName()) + " " + getErrorMessage(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Util.toast(SelectPlaylistActivity.this, msg, false);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.common_cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayPlaylistInfo(final Playlist playlist) {
|
||||||
|
String message = "Owner: " + playlist.getOwner() + "\nComments: " +
|
||||||
|
((playlist.getComment() == null) ? "" : playlist.getComment()) +
|
||||||
|
"\nSong Count: " + playlist.getSongCount() +
|
||||||
|
((playlist.getPublic() == null) ? "" : ("\nPublic: " + playlist.getPublic()) +
|
||||||
|
((playlist.getCreated() == null) ? "" : ("\nCreation Date: " + playlist.getCreated().replace('T', ' '))));
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.setTitle(playlist.getName())
|
||||||
|
.setMessage(message)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePlaylistInfo(final Playlist playlist) {
|
||||||
|
View dialogView = getLayoutInflater().inflate(R.layout.update_playlist, null);
|
||||||
|
final EditText nameBox = (EditText)dialogView.findViewById(R.id.get_playlist_name);
|
||||||
|
final EditText commentBox = (EditText)dialogView.findViewById(R.id.get_playlist_comment);
|
||||||
|
final CheckBox publicBox = (CheckBox)dialogView.findViewById(R.id.get_playlist_public);
|
||||||
|
|
||||||
|
nameBox.setText(playlist.getName());
|
||||||
|
commentBox.setText(playlist.getComment());
|
||||||
|
Boolean pub = playlist.getPublic();
|
||||||
|
if(pub == null) {
|
||||||
|
publicBox.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
publicBox.setChecked(pub);
|
||||||
|
}
|
||||||
|
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.setTitle(R.string.playlist_update_info)
|
||||||
|
.setView(dialogView)
|
||||||
|
.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
new LoadingTask<Void>(SelectPlaylistActivity.this, false) {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Throwable {
|
||||||
|
MusicService musicService = MusicServiceFactory.getMusicService(SelectPlaylistActivity.this);
|
||||||
|
musicService.updatePlaylist(playlist.getId(), nameBox.getText().toString(), commentBox.getText().toString(), publicBox.isChecked(), SelectPlaylistActivity.this, null);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(Void result) {
|
||||||
|
refresh();
|
||||||
|
Util.toast(SelectPlaylistActivity.this, getResources().getString(R.string.playlist_updated_info, playlist.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void error(Throwable error) {
|
||||||
|
String msg;
|
||||||
|
if (error instanceof OfflineException || error instanceof ServerTooOldException) {
|
||||||
|
msg = getErrorMessage(error);
|
||||||
|
} else {
|
||||||
|
msg = getResources().getString(R.string.playlist_updated_info_error, playlist.getName()) + " " + getErrorMessage(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Util.toast(SelectPlaylistActivity.this, msg, false);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.common_cancel, null)
|
||||||
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class GetDataTask extends AsyncTask<Void, Void, String[]> {
|
private class GetDataTask extends AsyncTask<Void, Void, String[]> {
|
||||||
|
|
|
@ -20,6 +20,7 @@ package com.thejoshwa.ultrasonic.androidapp.activity;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.CheckBoxPreference;
|
import android.preference.CheckBoxPreference;
|
||||||
import android.preference.EditTextPreference;
|
import android.preference.EditTextPreference;
|
||||||
|
@ -77,6 +78,7 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer
|
||||||
private ListPreference defaultArtists;
|
private ListPreference defaultArtists;
|
||||||
private CheckBoxPreference mediaButtonsEnabled;
|
private CheckBoxPreference mediaButtonsEnabled;
|
||||||
private CheckBoxPreference lockScreenEnabled;
|
private CheckBoxPreference lockScreenEnabled;
|
||||||
|
private CheckBoxPreference gaplessPlaybackEnabled;
|
||||||
private int maxServerCount = 10;
|
private int maxServerCount = 10;
|
||||||
private int minServerCount = 0;
|
private int minServerCount = 0;
|
||||||
|
|
||||||
|
@ -148,6 +150,7 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer
|
||||||
defaultAlbums = (ListPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS);
|
defaultAlbums = (ListPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS);
|
||||||
mediaButtonsEnabled = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_MEDIA_BUTTONS);
|
mediaButtonsEnabled = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_MEDIA_BUTTONS);
|
||||||
lockScreenEnabled = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS);
|
lockScreenEnabled = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS);
|
||||||
|
gaplessPlaybackEnabled = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK);
|
||||||
|
|
||||||
findPreference(Constants.PREFERENCES_KEY_CLEAR_SEARCH_HISTORY).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
findPreference(Constants.PREFERENCES_KEY_CLEAR_SEARCH_HISTORY).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -159,6 +162,11 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < 14) {
|
||||||
|
gaplessPlaybackEnabled.setChecked(false);
|
||||||
|
gaplessPlaybackEnabled.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
settings = PreferenceManager.getDefaultSharedPreferences(this );
|
settings = PreferenceManager.getDefaultSharedPreferences(this );
|
||||||
activeServers = settings.getInt(Constants.PREFERENCES_KEY_ACTIVE_SERVERS, 3);
|
activeServers = settings.getInt(Constants.PREFERENCES_KEY_ACTIVE_SERVERS, 3);
|
||||||
|
|
||||||
|
|
|
@ -20,14 +20,18 @@ package com.thejoshwa.ultrasonic.androidapp.activity;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.PixelFormat;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -35,27 +39,32 @@ import android.os.Environment;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.View.OnTouchListener;
|
import android.view.View.OnTouchListener;
|
||||||
|
import android.view.Window;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.R;
|
import com.thejoshwa.ultrasonic.androidapp.R;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory.Entry;
|
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory.Entry;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.PlayerState;
|
import com.thejoshwa.ultrasonic.androidapp.domain.PlayerState;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.domain.Playlist;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadService;
|
import com.thejoshwa.ultrasonic.androidapp.service.DownloadService;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadServiceImpl;
|
import com.thejoshwa.ultrasonic.androidapp.service.DownloadServiceImpl;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.service.OfflineException;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.service.ServerTooOldException;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
|
import com.thejoshwa.ultrasonic.androidapp.util.Constants;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.FileUtil;
|
import com.thejoshwa.ultrasonic.androidapp.util.FileUtil;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.ImageLoader;
|
import com.thejoshwa.ultrasonic.androidapp.util.ImageLoader;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.util.LoadingTask;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.ModalBackgroundTask;
|
import com.thejoshwa.ultrasonic.androidapp.util.ModalBackgroundTask;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.util.SilentBackgroundTask;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
||||||
|
|
||||||
import net.simonvt.menudrawer.MenuDrawer;
|
import net.simonvt.menudrawer.MenuDrawer;
|
||||||
|
@ -67,6 +76,7 @@ import net.simonvt.menudrawer.Position;
|
||||||
public class SubsonicTabActivity extends Activity implements OnClickListener{
|
public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
private static final String TAG = SubsonicTabActivity.class.getSimpleName();
|
private static final String TAG = SubsonicTabActivity.class.getSimpleName();
|
||||||
private static ImageLoader IMAGE_LOADER;
|
private static ImageLoader IMAGE_LOADER;
|
||||||
|
protected static String theme;
|
||||||
private static SubsonicTabActivity instance;
|
private static SubsonicTabActivity instance;
|
||||||
|
|
||||||
private boolean destroyed;
|
private boolean destroyed;
|
||||||
|
@ -78,7 +88,7 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
public MenuDrawer menuDrawer;
|
public MenuDrawer menuDrawer;
|
||||||
private int activePosition = 1;
|
private int activePosition = 1;
|
||||||
private int menuActiveViewId;
|
private int menuActiveViewId;
|
||||||
private View nowPlaying = null;
|
private View nowPlayingView = null;
|
||||||
View searchMenuItem = null;
|
View searchMenuItem = null;
|
||||||
View playlistsMenuItem = null;
|
View playlistsMenuItem = null;
|
||||||
View menuMain = null;
|
View menuMain = null;
|
||||||
|
@ -120,23 +130,12 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
if (activeView != null) {
|
if (activeView != null) {
|
||||||
menuDrawer.setActiveView(activeView);
|
menuDrawer.setActiveView(activeView);
|
||||||
}
|
}
|
||||||
|
|
||||||
instance = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostCreate(Bundle bundle) {
|
protected void onPostCreate(Bundle bundle) {
|
||||||
super.onPostCreate(bundle);
|
super.onPostCreate(bundle);
|
||||||
|
instance = this;
|
||||||
if (!nowPlayingHidden) {
|
|
||||||
showNowPlaying();
|
|
||||||
} else {
|
|
||||||
hideNowPlaying();
|
|
||||||
}
|
|
||||||
|
|
||||||
int visibility = Util.isOffline(this) ? View.GONE : View.VISIBLE;
|
|
||||||
searchMenuItem.setVisibility(visibility);
|
|
||||||
playlistsMenuItem.setVisibility(visibility);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -145,13 +144,18 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
applyTheme();
|
applyTheme();
|
||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
|
Util.registerMediaButtonEventReceiver(this);
|
||||||
|
|
||||||
|
// Make sure to update theme
|
||||||
|
if (theme != null && !theme.equals(Util.getTheme(this))) {
|
||||||
|
restart();
|
||||||
|
}
|
||||||
|
|
||||||
if (!nowPlayingHidden) {
|
if (!nowPlayingHidden) {
|
||||||
showNowPlaying();
|
showNowPlaying();
|
||||||
} else {
|
} else {
|
||||||
hideNowPlaying();
|
hideNowPlaying();
|
||||||
}
|
}
|
||||||
|
|
||||||
Util.registerMediaButtonEventReceiver(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -188,24 +192,35 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
return super.onKeyDown(keyCode, event);
|
return super.onKeyDown(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void restart() {
|
||||||
|
Intent intent = new Intent(this, this.getClass());
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
intent.putExtras(getIntent());
|
||||||
|
Util.startActivityWithoutTransition(this, intent);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void finish() {
|
public void finish() {
|
||||||
super.finish();
|
super.finish();
|
||||||
Util.disablePendingTransition(this);
|
Util.disablePendingTransition(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showNowPlaying()
|
public boolean isDestroyed() {
|
||||||
|
return destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showNowPlaying()
|
||||||
{
|
{
|
||||||
nowPlaying = findViewById(R.id.now_playing);
|
nowPlayingView = findViewById(R.id.now_playing);
|
||||||
|
|
||||||
if (!Util.getShowNowPlayingPreference(this)) {
|
if (!Util.getShowNowPlayingPreference(this)) {
|
||||||
if (nowPlaying != null) {
|
if (nowPlayingView != null) {
|
||||||
nowPlaying.setVisibility(View.GONE);
|
nowPlayingView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nowPlaying != null) {
|
if (nowPlayingView != null) {
|
||||||
final DownloadService downloadService = DownloadServiceImpl.getInstance();
|
final DownloadService downloadService = DownloadServiceImpl.getInstance();
|
||||||
|
|
||||||
if (downloadService != null) {
|
if (downloadService != null) {
|
||||||
|
@ -222,12 +237,12 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
hideNowPlaying();
|
hideNowPlaying();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageView nowPlayingControlPlay = (ImageView) nowPlaying.findViewById(R.id.now_playing_control_play);
|
ImageView nowPlayingControlPlay = (ImageView) nowPlayingView.findViewById(R.id.now_playing_control_play);
|
||||||
|
|
||||||
SwipeDetector swipeDetector = SwipeDetector.Create(SubsonicTabActivity.this, downloadService);
|
SwipeDetector swipeDetector = SwipeDetector.Create(SubsonicTabActivity.this, downloadService);
|
||||||
nowPlaying.setOnTouchListener(swipeDetector);
|
nowPlayingView.setOnTouchListener(swipeDetector);
|
||||||
|
|
||||||
nowPlaying.setOnClickListener(new View.OnClickListener() {
|
nowPlayingView.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
}
|
}
|
||||||
|
@ -253,27 +268,30 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showNowPlaying(final Context context, final DownloadServiceImpl downloadService, final MusicDirectory.Entry song, PlayerState playerState) {
|
private void showNowPlaying(final Context context, final DownloadServiceImpl downloadService, final MusicDirectory.Entry song, final PlayerState playerState) {
|
||||||
nowPlaying = findViewById(R.id.now_playing);
|
this.runOnUiThread( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
nowPlayingView = findViewById(R.id.now_playing);
|
||||||
|
|
||||||
if (!Util.getShowNowPlayingPreference(this)) {
|
if (!Util.getShowNowPlayingPreference(SubsonicTabActivity.this)) {
|
||||||
if (nowPlaying != null) {
|
if (nowPlayingView != null) {
|
||||||
nowPlaying.setVisibility(View.GONE);
|
nowPlayingView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nowPlaying != null) {
|
if (nowPlayingView != null) {
|
||||||
nowPlaying.setVisibility(View.VISIBLE);
|
nowPlayingView.setVisibility(View.VISIBLE);
|
||||||
nowPlayingHidden = false;
|
nowPlayingHidden = false;
|
||||||
|
|
||||||
String title = song.getTitle();
|
String title = song.getTitle();
|
||||||
String artist = song.getArtist();
|
String artist = song.getArtist();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ImageView nowPlayingImage = (ImageView) nowPlaying.findViewById(R.id.now_playing_image);
|
ImageView nowPlayingImage = (ImageView) nowPlayingView.findViewById(R.id.now_playing_image);
|
||||||
TextView nowPlayingTrack = (TextView) nowPlaying.findViewById(R.id.now_playing_trackname);
|
TextView nowPlayingTrack = (TextView) nowPlayingView.findViewById(R.id.now_playing_trackname);
|
||||||
TextView nowPlayingArtist = (TextView) nowPlaying.findViewById(R.id.now_playing_artist);
|
TextView nowPlayingArtist = (TextView) nowPlayingView.findViewById(R.id.now_playing_artist);
|
||||||
|
|
||||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||||
int imageSizeLarge = (int) Math.round(Math.min(metrics.widthPixels, metrics.heightPixels));
|
int imageSizeLarge = (int) Math.round(Math.min(metrics.widthPixels, metrics.heightPixels));
|
||||||
|
@ -316,23 +334,30 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
Log.w(TAG, "Failed to get notification cover art", x);
|
Log.w(TAG, "Failed to get notification cover art", x);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageView playButton = (ImageView) nowPlaying.findViewById(R.id.now_playing_control_play);
|
ImageView playButton = (ImageView) nowPlayingView.findViewById(R.id.now_playing_control_play);
|
||||||
|
|
||||||
if (playerState == PlayerState.PAUSED) {
|
if (playerState == PlayerState.PAUSED) {
|
||||||
playButton.setImageDrawable(Util.getDrawableFromAttribute(this, R.attr.media_play));
|
playButton.setImageDrawable(Util.getDrawableFromAttribute(SubsonicTabActivity.this, R.attr.media_play));
|
||||||
} else if (playerState == PlayerState.STARTED) {
|
} else if (playerState == PlayerState.STARTED) {
|
||||||
playButton.setImageDrawable(Util.getDrawableFromAttribute(this, R.attr.media_pause));
|
playButton.setImageDrawable(Util.getDrawableFromAttribute(SubsonicTabActivity.this, R.attr.media_pause));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void hideNowPlaying() {
|
public void hideNowPlaying() {
|
||||||
nowPlaying = findViewById(R.id.now_playing);
|
this.runOnUiThread( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
nowPlayingView = findViewById(R.id.now_playing);
|
||||||
|
|
||||||
if (nowPlaying != null) {
|
if (nowPlayingView != null) {
|
||||||
nowPlaying.setVisibility(View.GONE);
|
nowPlayingView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static SubsonicTabActivity getInstance() {
|
public static SubsonicTabActivity getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
|
@ -385,15 +410,31 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
return IMAGE_LOADER;
|
return IMAGE_LOADER;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void downloadRecursively(final String id, final boolean save, final boolean append, final boolean autoplay, final boolean playNext) {
|
public synchronized static ImageLoader getStaticImageLoader(Context context) {
|
||||||
ModalBackgroundTask<List<MusicDirectory.Entry>> task = new ModalBackgroundTask<List<MusicDirectory.Entry>>(this, false) {
|
if (IMAGE_LOADER == null) {
|
||||||
|
IMAGE_LOADER = new ImageLoader(context);
|
||||||
|
}
|
||||||
|
return IMAGE_LOADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void downloadRecursively(final String id, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background) {
|
||||||
|
downloadRecursively(id, "", true, save, append, autoplay, shuffle, background);
|
||||||
|
}
|
||||||
|
protected void downloadPlaylist(final String id, final String name, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background) {
|
||||||
|
downloadRecursively(id, name, false, save, append, autoplay, shuffle, background);
|
||||||
|
}
|
||||||
|
protected void downloadRecursively(final String id, final String name, final boolean isDirectory, final boolean save, final boolean append, final boolean autoplay, final boolean shuffle, final boolean background) {
|
||||||
|
ModalBackgroundTask<List<MusicDirectory.Entry>> task = new ModalBackgroundTask<List<MusicDirectory.Entry>>(this, false) {
|
||||||
private static final int MAX_SONGS = 500;
|
private static final int MAX_SONGS = 500;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<MusicDirectory.Entry> doInBackground() throws Throwable {
|
protected List<MusicDirectory.Entry> doInBackground() throws Throwable {
|
||||||
MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this);
|
MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this);
|
||||||
MusicDirectory root = musicService.getMusicDirectory(id, false, SubsonicTabActivity.this, this);
|
MusicDirectory root;
|
||||||
|
if(isDirectory)
|
||||||
|
root = musicService.getMusicDirectory(id, name, false, SubsonicTabActivity.this, this);
|
||||||
|
else
|
||||||
|
root = musicService.getPlaylist(id, name, SubsonicTabActivity.this, this);
|
||||||
List<MusicDirectory.Entry> songs = new LinkedList<MusicDirectory.Entry>();
|
List<MusicDirectory.Entry> songs = new LinkedList<MusicDirectory.Entry>();
|
||||||
getSongsRecursively(root, songs);
|
getSongsRecursively(root, songs);
|
||||||
return songs;
|
return songs;
|
||||||
|
@ -411,7 +452,7 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
}
|
}
|
||||||
for (MusicDirectory.Entry dir : parent.getChildren(true, false)) {
|
for (MusicDirectory.Entry dir : parent.getChildren(true, false)) {
|
||||||
MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this);
|
MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this);
|
||||||
getSongsRecursively(musicService.getMusicDirectory(dir.getId(), false, SubsonicTabActivity.this, this), songs);
|
getSongsRecursively(musicService.getMusicDirectory(dir.getId(), dir.getTitle(), false, SubsonicTabActivity.this, this), songs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,19 +460,99 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
protected void done(List<MusicDirectory.Entry> songs) {
|
protected void done(List<MusicDirectory.Entry> songs) {
|
||||||
DownloadService downloadService = getDownloadService();
|
DownloadService downloadService = getDownloadService();
|
||||||
if (!songs.isEmpty() && downloadService != null) {
|
if (!songs.isEmpty() && downloadService != null) {
|
||||||
if (!append && !playNext) {
|
if (!append) {
|
||||||
downloadService.clear();
|
downloadService.clear();
|
||||||
}
|
}
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
downloadService.download(songs, save, autoplay, playNext);
|
if(!background) {
|
||||||
|
downloadService.download(songs, save, autoplay, false, shuffle);
|
||||||
|
if (!append && Util.getShouldTransitionOnPlaybackPreference(SubsonicTabActivity.this)) {
|
||||||
Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class);
|
Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
downloadService.downloadBackground(songs, save);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
task.execute();
|
task.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void addToPlaylist(final List<MusicDirectory.Entry> songs) {
|
||||||
|
if(songs.isEmpty()) {
|
||||||
|
Util.toast(this, "No songs selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new LoadingTask<List<Playlist>>(this, true) {
|
||||||
|
@Override
|
||||||
|
protected List<Playlist> doInBackground() throws Throwable {
|
||||||
|
MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this);
|
||||||
|
return musicService.getPlaylists(false, SubsonicTabActivity.this, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(final List<Playlist> playlists) {
|
||||||
|
List<String> names = new ArrayList<String>();
|
||||||
|
for(Playlist playlist: playlists) {
|
||||||
|
names.add(playlist.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(SubsonicTabActivity.this);
|
||||||
|
builder.setTitle("Add to Playlist")
|
||||||
|
.setItems(names.toArray(new CharSequence[names.size()]), new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
addToPlaylist(playlists.get(which), songs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AlertDialog dialog = builder.create();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void error(Throwable error) {
|
||||||
|
String msg;
|
||||||
|
if (error instanceof OfflineException || error instanceof ServerTooOldException) {
|
||||||
|
msg = getErrorMessage(error);
|
||||||
|
} else {
|
||||||
|
msg = getResources().getString(R.string.playlist_error) + " " + getErrorMessage(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Util.toast(SubsonicTabActivity.this, msg, false);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToPlaylist(final Playlist playlist, final List<MusicDirectory.Entry> songs) {
|
||||||
|
new SilentBackgroundTask<Void>(this) {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Throwable {
|
||||||
|
MusicService musicService = MusicServiceFactory.getMusicService(SubsonicTabActivity.this);
|
||||||
|
musicService.addToPlaylist(playlist.getId(), songs, SubsonicTabActivity.this, null);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done(Void result) {
|
||||||
|
Util.toast(SubsonicTabActivity.this, getResources().getString(R.string.updated_playlist, songs.size(), playlist.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void error(Throwable error) {
|
||||||
|
String msg;
|
||||||
|
if (error instanceof OfflineException || error instanceof ServerTooOldException) {
|
||||||
|
msg = getErrorMessage(error);
|
||||||
|
} else {
|
||||||
|
msg = getResources().getString(R.string.updated_playlist_error, playlist.getName()) + " " + getErrorMessage(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Util.toast(SubsonicTabActivity.this, msg, false);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
private void setUncaughtExceptionHandler() {
|
private void setUncaughtExceptionHandler() {
|
||||||
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
||||||
if (!(handler instanceof SubsonicUncaughtExceptionHandler)) {
|
if (!(handler instanceof SubsonicUncaughtExceptionHandler)) {
|
||||||
|
@ -481,7 +602,6 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
//menuDrawer.setActiveView(v);
|
|
||||||
menuActiveViewId = v.getId();
|
menuActiveViewId = v.getId();
|
||||||
|
|
||||||
Intent intent;
|
Intent intent;
|
||||||
|
@ -620,4 +740,3 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,8 @@ public class EqualizerController {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private Equalizer equalizer;
|
private Equalizer equalizer;
|
||||||
|
private boolean released = false;
|
||||||
|
private int audioSessionId = 0;
|
||||||
|
|
||||||
// Class initialization fails when this throws an exception.
|
// Class initialization fails when this throws an exception.
|
||||||
static {
|
static {
|
||||||
|
@ -58,7 +60,8 @@ public class EqualizerController {
|
||||||
public EqualizerController(Context context, MediaPlayer mediaPlayer) {
|
public EqualizerController(Context context, MediaPlayer mediaPlayer) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
try {
|
try {
|
||||||
equalizer = new Equalizer(0, mediaPlayer.getAudioSessionId());
|
audioSessionId = mediaPlayer.getAudioSessionId();
|
||||||
|
equalizer = new Equalizer(0, audioSessionId);
|
||||||
} catch (Throwable x) {
|
} catch (Throwable x) {
|
||||||
Log.w(TAG, "Failed to create equalizer.", x);
|
Log.w(TAG, "Failed to create equalizer.", x);
|
||||||
}
|
}
|
||||||
|
@ -97,11 +100,21 @@ public class EqualizerController {
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
if (isAvailable()) {
|
if (isAvailable()) {
|
||||||
|
released = true;
|
||||||
equalizer.release();
|
equalizer.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Equalizer getEqualizer() {
|
public Equalizer getEqualizer() {
|
||||||
|
if(released) {
|
||||||
|
released = false;
|
||||||
|
try {
|
||||||
|
equalizer = new Equalizer(0, audioSessionId);
|
||||||
|
} catch (Throwable x) {
|
||||||
|
equalizer = null;
|
||||||
|
Log.w(TAG, "Failed to create equalizer.", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
return equalizer;
|
return equalizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +123,7 @@ public class EqualizerController {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private static final long serialVersionUID = 1477565229521401809L;
|
private static final long serialVersionUID = 626565082425206061L;
|
||||||
private final short[] bandLevels;
|
private final short[] bandLevels;
|
||||||
private short preset;
|
private short preset;
|
||||||
private final boolean enabled;
|
private final boolean enabled;
|
||||||
|
@ -139,4 +152,3 @@ public class EqualizerController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@ public class VisualizerController {
|
||||||
private static final int PREFERRED_CAPTURE_SIZE = 128; // Must be a power of two.
|
private static final int PREFERRED_CAPTURE_SIZE = 128; // Must be a power of two.
|
||||||
|
|
||||||
private Visualizer visualizer;
|
private Visualizer visualizer;
|
||||||
|
private boolean released = false;
|
||||||
|
private int audioSessionId = 0;
|
||||||
|
|
||||||
// Class initialization fails when this throws an exception.
|
// Class initialization fails when this throws an exception.
|
||||||
static {
|
static {
|
||||||
|
@ -54,7 +56,8 @@ public class VisualizerController {
|
||||||
|
|
||||||
public VisualizerController(Context context, MediaPlayer mediaPlayer) {
|
public VisualizerController(Context context, MediaPlayer mediaPlayer) {
|
||||||
try {
|
try {
|
||||||
visualizer = new Visualizer(mediaPlayer.getAudioSessionId());
|
audioSessionId = mediaPlayer.getAudioSessionId();
|
||||||
|
visualizer = new Visualizer(audioSessionId);
|
||||||
} catch (Throwable x) {
|
} catch (Throwable x) {
|
||||||
Log.w(TAG, "Failed to create visualizer.", x);
|
Log.w(TAG, "Failed to create visualizer.", x);
|
||||||
}
|
}
|
||||||
|
@ -78,11 +81,21 @@ public class VisualizerController {
|
||||||
public void release() {
|
public void release() {
|
||||||
if (isAvailable()) {
|
if (isAvailable()) {
|
||||||
visualizer.release();
|
visualizer.release();
|
||||||
|
released = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Visualizer getVisualizer() {
|
public Visualizer getVisualizer() {
|
||||||
|
if(released) {
|
||||||
|
released = false;
|
||||||
|
try {
|
||||||
|
visualizer = new Visualizer(audioSessionId);
|
||||||
|
} catch (Throwable x) {
|
||||||
|
visualizer = null;
|
||||||
|
Log.w(TAG, "Failed to create visualizer.", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return visualizer;
|
return visualizer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ public class Artist implements Serializable {
|
||||||
private String id;
|
private String id;
|
||||||
private String name;
|
private String name;
|
||||||
private String index;
|
private String index;
|
||||||
|
private int closeness;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
|
@ -57,6 +58,14 @@ public class Artist implements Serializable {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCloseness() {
|
||||||
|
return closeness;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCloseness(int closeness) {
|
||||||
|
this.closeness = closeness;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return name;
|
return name;
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
package com.thejoshwa.ultrasonic.androidapp.domain;
|
package com.thejoshwa.ultrasonic.androidapp.domain;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@ -29,12 +30,12 @@ public class Indexes implements Serializable {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private static final long serialVersionUID = -9186563899711652646L;
|
private static final long serialVersionUID = 8156117238598414701L;
|
||||||
private final long lastModified;
|
private final long lastModified;
|
||||||
private final List<Artist> shortcuts;
|
private final List<com.thejoshwa.ultrasonic.androidapp.domain.Artist> shortcuts;
|
||||||
private final List<Artist> artists;
|
private final List<com.thejoshwa.ultrasonic.androidapp.domain.Artist> artists;
|
||||||
|
|
||||||
public Indexes(long lastModified, List<Artist> shortcuts, List<Artist> artists) {
|
public Indexes(long lastModified, List<com.thejoshwa.ultrasonic.androidapp.domain.Artist> shortcuts, List<com.thejoshwa.ultrasonic.androidapp.domain.Artist> artists) {
|
||||||
this.lastModified = lastModified;
|
this.lastModified = lastModified;
|
||||||
this.shortcuts = shortcuts;
|
this.shortcuts = shortcuts;
|
||||||
this.artists = artists;
|
this.artists = artists;
|
||||||
|
@ -44,11 +45,11 @@ public class Indexes implements Serializable {
|
||||||
return lastModified;
|
return lastModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Artist> getShortcuts() {
|
public List<com.thejoshwa.ultrasonic.androidapp.domain.Artist> getShortcuts() {
|
||||||
return shortcuts;
|
return shortcuts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Artist> getArtists() {
|
public List<com.thejoshwa.ultrasonic.androidapp.domain.Artist> getArtists() {
|
||||||
return artists;
|
return artists;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -61,6 +61,10 @@ public class MusicDirectory {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Entry implements Serializable {
|
public static class Entry implements Serializable {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = -3339106650010798108L;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@ -85,6 +89,7 @@ public class MusicDirectory {
|
||||||
private boolean video;
|
private boolean video;
|
||||||
private boolean starred;
|
private boolean starred;
|
||||||
private Integer discNumber;
|
private Integer discNumber;
|
||||||
|
private int closeness;
|
||||||
|
|
||||||
public Integer getDiscNumber() {
|
public Integer getDiscNumber() {
|
||||||
return discNumber;
|
return discNumber;
|
||||||
|
@ -258,6 +263,14 @@ public class MusicDirectory {
|
||||||
this.video = video;
|
this.video = video;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getCloseness() {
|
||||||
|
return closeness;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCloseness(int closeness) {
|
||||||
|
this.closeness = closeness;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
|
|
|
@ -28,14 +28,28 @@ public class Playlist implements Serializable {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private static final long serialVersionUID = -6658599528577231609L;
|
private static final long serialVersionUID = -4160515427075433798L;
|
||||||
private String id;
|
private String id;
|
||||||
private String name;
|
private String name;
|
||||||
|
private String owner;
|
||||||
|
private String comment;
|
||||||
|
private String songCount;
|
||||||
|
private String created;
|
||||||
|
private Boolean pub;
|
||||||
|
|
||||||
public Playlist(String id, String name) {
|
public Playlist(String id, String name) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
public Playlist(String id, String name, String owner, String comment, String songCount, String created, String pub) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.owner = (owner == null) ? "" : owner;
|
||||||
|
this.comment = (comment == null) ? "" : comment;
|
||||||
|
this.songCount = (songCount == null) ? "" : songCount;
|
||||||
|
this.created = (created == null) ? "" : created;
|
||||||
|
this.pub = (pub == null) ? null : (pub.equals("true"));
|
||||||
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
|
@ -53,6 +67,45 @@ public class Playlist implements Serializable {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getOwner() {
|
||||||
|
return this.owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOwner(String owner) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComment() {
|
||||||
|
return this.comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComment(String comment) {
|
||||||
|
this.comment = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSongCount() {
|
||||||
|
return this.songCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSongCount(String songCount) {
|
||||||
|
this.songCount = songCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreated() {
|
||||||
|
return this.created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(String created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getPublic() {
|
||||||
|
return this.pub;
|
||||||
|
}
|
||||||
|
public void setPublic(Boolean pub) {
|
||||||
|
this.pub = pub;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return name;
|
return name;
|
||||||
|
|
|
@ -54,7 +54,7 @@ public class CachedMusicService implements MusicService {
|
||||||
private final LRUCache<String, TimeLimitedCache<MusicDirectory>> cachedMusicDirectories;
|
private final LRUCache<String, TimeLimitedCache<MusicDirectory>> cachedMusicDirectories;
|
||||||
private final TimeLimitedCache<Boolean> cachedLicenseValid = new TimeLimitedCache<Boolean>(120, TimeUnit.SECONDS);
|
private final TimeLimitedCache<Boolean> cachedLicenseValid = new TimeLimitedCache<Boolean>(120, TimeUnit.SECONDS);
|
||||||
private final TimeLimitedCache<Indexes> cachedIndexes = new TimeLimitedCache<Indexes>(60 * 60, TimeUnit.SECONDS);
|
private final TimeLimitedCache<Indexes> cachedIndexes = new TimeLimitedCache<Indexes>(60 * 60, TimeUnit.SECONDS);
|
||||||
private final TimeLimitedCache<List<Playlist>> cachedPlaylists = new TimeLimitedCache<List<Playlist>>(60, TimeUnit.SECONDS);
|
private final TimeLimitedCache<List<Playlist>> cachedPlaylists = new TimeLimitedCache<List<Playlist>>(3600, TimeUnit.SECONDS);
|
||||||
private final TimeLimitedCache<List<MusicFolder>> cachedMusicFolders = new TimeLimitedCache<List<MusicFolder>>(10 * 3600, TimeUnit.SECONDS);
|
private final TimeLimitedCache<List<MusicFolder>> cachedMusicFolders = new TimeLimitedCache<List<MusicFolder>>(10 * 3600, TimeUnit.SECONDS);
|
||||||
private final TimeLimitedCache<List<Genre>> cachedGenres = new TimeLimitedCache<List<Genre>>(10 * 3600, TimeUnit.SECONDS);
|
private final TimeLimitedCache<List<Genre>> cachedGenres = new TimeLimitedCache<List<Genre>>(10 * 3600, TimeUnit.SECONDS);
|
||||||
private String restUrl;
|
private String restUrl;
|
||||||
|
@ -82,11 +82,14 @@ public class CachedMusicService implements MusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<MusicFolder> getMusicFolders(Context context, ProgressListener progressListener) throws Exception {
|
public List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||||
checkSettingsChanged(context);
|
checkSettingsChanged(context);
|
||||||
|
if (refresh) {
|
||||||
|
cachedMusicFolders.clear();
|
||||||
|
}
|
||||||
List<MusicFolder> result = cachedMusicFolders.get();
|
List<MusicFolder> result = cachedMusicFolders.get();
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = musicService.getMusicFolders(context, progressListener);
|
result = musicService.getMusicFolders(refresh, context, progressListener);
|
||||||
cachedMusicFolders.set(result);
|
cachedMusicFolders.set(result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -109,12 +112,12 @@ public class CachedMusicService implements MusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MusicDirectory getMusicDirectory(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
public MusicDirectory getMusicDirectory(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||||
checkSettingsChanged(context);
|
checkSettingsChanged(context);
|
||||||
TimeLimitedCache<MusicDirectory> cache = refresh ? null : cachedMusicDirectories.get(id);
|
TimeLimitedCache<MusicDirectory> cache = refresh ? null : cachedMusicDirectories.get(id);
|
||||||
MusicDirectory dir = cache == null ? null : cache.get();
|
MusicDirectory dir = cache == null ? null : cache.get();
|
||||||
if (dir == null) {
|
if (dir == null) {
|
||||||
dir = musicService.getMusicDirectory(id, refresh, context, progressListener);
|
dir = musicService.getMusicDirectory(id, name, refresh, context, progressListener);
|
||||||
cache = new TimeLimitedCache<MusicDirectory>(TTL_MUSIC_DIR, TimeUnit.SECONDS);
|
cache = new TimeLimitedCache<MusicDirectory>(TTL_MUSIC_DIR, TimeUnit.SECONDS);
|
||||||
cache.set(dir);
|
cache.set(dir);
|
||||||
cachedMusicDirectories.put(id, cache);
|
cachedMusicDirectories.put(id, cache);
|
||||||
|
@ -128,8 +131,8 @@ public class CachedMusicService implements MusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
|
public MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception {
|
||||||
return musicService.getPlaylist(id, context, progressListener);
|
return musicService.getPlaylist(id, name, context, progressListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -145,9 +148,30 @@ public class CachedMusicService implements MusicService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context, ProgressListener progressListener) throws Exception {
|
public void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
cachedPlaylists.clear();
|
||||||
musicService.createPlaylist(id, name, entries, context, progressListener);
|
musicService.createPlaylist(id, name, entries, context, progressListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
musicService.deletePlaylist(id, context, progressListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addToPlaylist(String id, List<MusicDirectory.Entry> toAdd, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
musicService.addToPlaylist(id, toAdd, context, progressListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeFromPlaylist(String id, List<Integer> toRemove, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
musicService.removeFromPlaylist(id, toRemove, context, progressListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
musicService.updatePlaylist(id, name, comment, pub, context, progressListener);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
|
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
|
||||||
return musicService.getLyrics(artist, title, context, progressListener);
|
return musicService.getLyrics(artist, title, context, progressListener);
|
||||||
|
@ -194,8 +218,13 @@ public class CachedMusicService implements MusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getVideoUrl(Context context, String id) {
|
public String getVideoUrl(int maxBitrate, Context context, String id) {
|
||||||
return musicService.getVideoUrl(context, id);
|
return musicService.getVideoUrl(maxBitrate, context, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVideoStreamUrl(int maxBitrate, Context context, String id) {
|
||||||
|
return musicService.getVideoStreamUrl(maxBitrate, context, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -56,6 +57,9 @@ public class DownloadFile {
|
||||||
private boolean save;
|
private boolean save;
|
||||||
private boolean failed;
|
private boolean failed;
|
||||||
private int bitRate;
|
private int bitRate;
|
||||||
|
private boolean isPlaying = false;
|
||||||
|
private boolean saveWhenDone = false;
|
||||||
|
private boolean completeWhenDone = false;
|
||||||
|
|
||||||
public DownloadFile(Context context, MusicDirectory.Entry song, boolean save) {
|
public DownloadFile(Context context, MusicDirectory.Entry song, boolean save) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
@ -63,8 +67,10 @@ public class DownloadFile {
|
||||||
this.save = save;
|
this.save = save;
|
||||||
saveFile = FileUtil.getSongFile(context, song);
|
saveFile = FileUtil.getSongFile(context, song);
|
||||||
bitRate = Util.getMaxBitrate(context);
|
bitRate = Util.getMaxBitrate(context);
|
||||||
partialFile = new File(saveFile.getParent(), FileUtil.getBaseName(saveFile.getName()) + "." + bitRate + ".partial." + FileUtil.getExtension(saveFile.getName()));
|
partialFile = new File(saveFile.getParent(), FileUtil.getBaseName(saveFile.getName()) +
|
||||||
completeFile = new File(saveFile.getParent(), FileUtil.getBaseName(saveFile.getName()) + ".complete." + FileUtil.getExtension(saveFile.getName()));
|
".partial." + FileUtil.getExtension(saveFile.getName()));
|
||||||
|
completeFile = new File(saveFile.getParent(), FileUtil.getBaseName(saveFile.getName()) +
|
||||||
|
".complete." + FileUtil.getExtension(saveFile.getName()));
|
||||||
mediaStoreService = new MediaStoreService(context);
|
mediaStoreService = new MediaStoreService(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,31 +78,25 @@ public class DownloadFile {
|
||||||
return song;
|
return song;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOffline() {
|
|
||||||
return Util.isOffline(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isStreamProxyEnabled() {
|
|
||||||
return Util.isStreamProxyEnabled(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the effective bit rate.
|
* Returns the effective bit rate.
|
||||||
*/
|
*/
|
||||||
public int getBitRate() {
|
public int getBitRate() {
|
||||||
|
if(!partialFile.exists()) {
|
||||||
|
bitRate = Util.getMaxBitrate(context);
|
||||||
|
}
|
||||||
if (bitRate > 0) {
|
if (bitRate > 0) {
|
||||||
return bitRate;
|
return bitRate;
|
||||||
}
|
}
|
||||||
return song.getBitRate() == null ? 160 : song.getBitRate();
|
return song.getBitRate() == null ? 160 : song.getBitRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBufferLength() {
|
|
||||||
return Util.getBufferLength(this.context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void download() {
|
public synchronized void download() {
|
||||||
FileUtil.createDirectoryForParent(saveFile);
|
FileUtil.createDirectoryForParent(saveFile);
|
||||||
failed = false;
|
failed = false;
|
||||||
|
if(!partialFile.exists()) {
|
||||||
|
bitRate = Util.getMaxBitrate(context);
|
||||||
|
}
|
||||||
downloadTask = new DownloadTask();
|
downloadTask = new DownloadTask();
|
||||||
downloadTask.start();
|
downloadTask.start();
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ public class DownloadFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean isWorkDone() {
|
public synchronized boolean isWorkDone() {
|
||||||
return saveFile.exists() || (completeFile.exists() && !save);
|
return saveFile.exists() || (completeFile.exists() && !save) || saveWhenDone || completeWhenDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean isDownloading() {
|
public synchronized boolean isDownloading() {
|
||||||
|
@ -192,6 +192,30 @@ public class DownloadFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPlaying(boolean isPlaying) {
|
||||||
|
try {
|
||||||
|
if(saveWhenDone && isPlaying == false) {
|
||||||
|
Util.renameFile(completeFile, saveFile);
|
||||||
|
saveWhenDone = false;
|
||||||
|
} else if(completeWhenDone && isPlaying == false) {
|
||||||
|
if(save) {
|
||||||
|
Util.renameFile(partialFile, saveFile);
|
||||||
|
mediaStoreService.saveInMediaStore(DownloadFile.this);
|
||||||
|
} else {
|
||||||
|
Util.renameFile(partialFile, completeFile);
|
||||||
|
}
|
||||||
|
completeWhenDone = false;
|
||||||
|
}
|
||||||
|
} catch(IOException ex) {
|
||||||
|
Log.w(TAG, "Failed to rename file " + completeFile + " to " + saveFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isPlaying = isPlaying;
|
||||||
|
}
|
||||||
|
public boolean getPlaying() {
|
||||||
|
return isPlaying;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "DownloadFile (" + song + ")";
|
return "DownloadFile (" + song + ")";
|
||||||
|
@ -205,7 +229,7 @@ public class DownloadFile {
|
||||||
InputStream in = null;
|
InputStream in = null;
|
||||||
FileOutputStream out = null;
|
FileOutputStream out = null;
|
||||||
PowerManager.WakeLock wakeLock = null;
|
PowerManager.WakeLock wakeLock = null;
|
||||||
|
WifiManager.WifiLock wifiLock = null;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (Util.isScreenLitOnDownload(context)) {
|
if (Util.isScreenLitOnDownload(context)) {
|
||||||
|
@ -215,25 +239,36 @@ public class DownloadFile {
|
||||||
Log.i(TAG, "Acquired wake lock " + wakeLock);
|
Log.i(TAG, "Acquired wake lock " + wakeLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saveFile.exists() && saveFile.length() == song.getSize()) {
|
wifiLock = Util.createWifiLock(context, toString());
|
||||||
|
wifiLock.acquire();
|
||||||
|
|
||||||
|
if (saveFile.exists()) {
|
||||||
Log.i(TAG, saveFile + " already exists. Skipping.");
|
Log.i(TAG, saveFile + " already exists. Skipping.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (completeFile.exists() && completeFile.length() == song.getSize()) {
|
if (completeFile.exists()) {
|
||||||
if (save) {
|
if (save) {
|
||||||
Util.atomicCopy(completeFile, saveFile);
|
if(isPlaying) {
|
||||||
|
saveWhenDone = true;
|
||||||
|
} else {
|
||||||
|
Util.renameFile(completeFile, saveFile);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, completeFile + " already exists. Skipping.");
|
Log.i(TAG, completeFile + " already exists. Skipping.");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isOffline()) {
|
|
||||||
Log.i(TAG, "We are offline. Skipping.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||||
|
|
||||||
|
// Some devices seem to throw error on partial file which doesn't exist
|
||||||
|
boolean compare;
|
||||||
|
try {
|
||||||
|
compare = (bitRate == 0) || (song.getDuration() == 0) || (partialFile.length() == 0) || (bitRate * song.getDuration() * 1000 / 8) > partialFile.length();
|
||||||
|
} catch(Exception e) {
|
||||||
|
compare = true;
|
||||||
|
}
|
||||||
|
if(compare) {
|
||||||
// Attempt partial HTTP GET, appending to the file if it exists.
|
// Attempt partial HTTP GET, appending to the file if it exists.
|
||||||
HttpResponse response = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this);
|
HttpResponse response = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this);
|
||||||
in = response.getEntity().getContent();
|
in = response.getEntity().getContent();
|
||||||
|
@ -253,12 +288,17 @@ public class DownloadFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadAndSaveCoverArt(musicService);
|
downloadAndSaveCoverArt(musicService);
|
||||||
|
}
|
||||||
|
|
||||||
if (save) {
|
if(isPlaying) {
|
||||||
Util.atomicCopy(partialFile, saveFile);
|
completeWhenDone = true;
|
||||||
|
} else {
|
||||||
|
if(save) {
|
||||||
|
Util.renameFile(partialFile, saveFile);
|
||||||
mediaStoreService.saveInMediaStore(DownloadFile.this);
|
mediaStoreService.saveInMediaStore(DownloadFile.this);
|
||||||
} else {
|
} else {
|
||||||
Util.atomicCopy(partialFile, completeFile);
|
Util.renameFile(partialFile, completeFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
|
@ -277,7 +317,13 @@ public class DownloadFile {
|
||||||
wakeLock.release();
|
wakeLock.release();
|
||||||
Log.i(TAG, "Released wake lock " + wakeLock);
|
Log.i(TAG, "Released wake lock " + wakeLock);
|
||||||
}
|
}
|
||||||
new CacheCleaner(context, DownloadServiceImpl.getInstance()).clean();
|
if (wifiLock != null) {
|
||||||
|
wifiLock.release();
|
||||||
|
}
|
||||||
|
new CacheCleaner(context, DownloadServiceImpl.getInstance()).cleanSpace();
|
||||||
|
if(DownloadServiceImpl.getInstance() != null) {
|
||||||
|
((DownloadServiceImpl)DownloadServiceImpl.getInstance()).checkDownloads();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,9 @@ import com.thejoshwa.ultrasonic.androidapp.domain.RepeatMode;
|
||||||
*/
|
*/
|
||||||
public interface DownloadService {
|
public interface DownloadService {
|
||||||
|
|
||||||
void download(List<MusicDirectory.Entry> songs, boolean save, boolean autoplay, boolean playNext);
|
void download(List<MusicDirectory.Entry> songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle);
|
||||||
|
|
||||||
|
void downloadBackground(List<MusicDirectory.Entry> songs, boolean save);
|
||||||
|
|
||||||
void setShufflePlayEnabled(boolean enabled);
|
void setShufflePlayEnabled(boolean enabled);
|
||||||
|
|
||||||
|
@ -50,18 +52,30 @@ public interface DownloadService {
|
||||||
|
|
||||||
boolean getShowVisualization();
|
boolean getShowVisualization();
|
||||||
|
|
||||||
|
boolean getEqualizerAvailable();
|
||||||
|
|
||||||
|
boolean getVisualizerAvailable();
|
||||||
|
|
||||||
void setShowVisualization(boolean showVisualization);
|
void setShowVisualization(boolean showVisualization);
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
void clearBackground();
|
||||||
|
|
||||||
void clearIncomplete();
|
void clearIncomplete();
|
||||||
|
|
||||||
int size();
|
int size();
|
||||||
|
|
||||||
|
void remove(int which);
|
||||||
|
|
||||||
void remove(DownloadFile downloadFile);
|
void remove(DownloadFile downloadFile);
|
||||||
|
|
||||||
|
List<DownloadFile> getSongs();
|
||||||
|
|
||||||
List<DownloadFile> getDownloads();
|
List<DownloadFile> getDownloads();
|
||||||
|
|
||||||
|
List<DownloadFile> getBackgroundDownloads();
|
||||||
|
|
||||||
int getCurrentPlayingIndex();
|
int getCurrentPlayingIndex();
|
||||||
|
|
||||||
DownloadFile getCurrentPlaying();
|
DownloadFile getCurrentPlaying();
|
||||||
|
@ -113,4 +127,8 @@ public interface DownloadService {
|
||||||
void adjustJukeboxVolume(boolean up);
|
void adjustJukeboxVolume(boolean up);
|
||||||
|
|
||||||
void togglePlayPause();
|
void togglePlayPause();
|
||||||
|
|
||||||
|
void setVolume(float volume);
|
||||||
|
|
||||||
|
void swap(boolean mainList, int from, int to);
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -24,11 +24,14 @@ import java.util.List;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
import android.media.RemoteControlClient;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.telephony.PhoneStateListener;
|
import android.telephony.PhoneStateListener;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -53,6 +56,7 @@ public class DownloadServiceLifecycleSupport {
|
||||||
private BroadcastReceiver ejectEventReceiver;
|
private BroadcastReceiver ejectEventReceiver;
|
||||||
private PhoneStateListener phoneStateListener;
|
private PhoneStateListener phoneStateListener;
|
||||||
private boolean externalStorageAvailable= true;
|
private boolean externalStorageAvailable= true;
|
||||||
|
private ReentrantLock lock = new ReentrantLock();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This receiver manages the intent that could come from other applications.
|
* This receiver manages the intent that could come from other applications.
|
||||||
|
@ -73,7 +77,7 @@ public class DownloadServiceLifecycleSupport {
|
||||||
} else if (DownloadServiceImpl.CMD_PAUSE.equals(action)) {
|
} else if (DownloadServiceImpl.CMD_PAUSE.equals(action)) {
|
||||||
downloadService.pause();
|
downloadService.pause();
|
||||||
} else if (DownloadServiceImpl.CMD_STOP.equals(action)) {
|
} else if (DownloadServiceImpl.CMD_STOP.equals(action)) {
|
||||||
downloadService.stop();
|
downloadService.pause();
|
||||||
downloadService.seekTo(0);
|
downloadService.seekTo(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,7 +100,7 @@ public class DownloadServiceLifecycleSupport {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
executorService = Executors.newScheduledThreadPool(2);
|
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||||
executorService.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS);
|
executorService.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
// Pause when headset is unplugged.
|
// Pause when headset is unplugged.
|
||||||
|
@ -105,9 +109,11 @@ public class DownloadServiceLifecycleSupport {
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
Log.i(TAG, "Headset event for: " + intent.getExtras().get("name"));
|
Log.i(TAG, "Headset event for: " + intent.getExtras().get("name"));
|
||||||
if (intent.getExtras().getInt("state") == 0) {
|
if (intent.getExtras().getInt("state") == 0) {
|
||||||
|
if(!downloadService.isJukeboxEnabled()) {
|
||||||
downloadService.pause();
|
downloadService.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
downloadService.registerReceiver(headsetEventReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
|
downloadService.registerReceiver(headsetEventReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
|
||||||
|
|
||||||
|
@ -163,12 +169,11 @@ public class DownloadServiceLifecycleSupport {
|
||||||
|
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
serializeDownloadQueue();
|
serializeDownloadQueueNow();
|
||||||
downloadService.clear(false);
|
downloadService.clear(false);
|
||||||
downloadService.unregisterReceiver(ejectEventReceiver);
|
downloadService.unregisterReceiver(ejectEventReceiver);
|
||||||
downloadService.unregisterReceiver(headsetEventReceiver);
|
downloadService.unregisterReceiver(headsetEventReceiver);
|
||||||
downloadService.unregisterReceiver(intentReceiver);
|
downloadService.unregisterReceiver(intentReceiver);
|
||||||
Util.unregisterMediaButtonEventReceiver(downloadService);
|
|
||||||
|
|
||||||
TelephonyManager telephonyManager = (TelephonyManager) downloadService.getSystemService(Context.TELEPHONY_SERVICE);
|
TelephonyManager telephonyManager = (TelephonyManager) downloadService.getSystemService(Context.TELEPHONY_SERVICE);
|
||||||
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
|
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
|
||||||
|
@ -179,8 +184,13 @@ public class DownloadServiceLifecycleSupport {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void serializeDownloadQueue() {
|
public void serializeDownloadQueue() {
|
||||||
|
new SerializeTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void serializeDownloadQueueNow() {
|
||||||
|
List<DownloadFile> songs = new ArrayList<DownloadFile>(downloadService.getSongs());
|
||||||
State state = new State();
|
State state = new State();
|
||||||
for (DownloadFile downloadFile : downloadService.getDownloads()) {
|
for (DownloadFile downloadFile : songs) {
|
||||||
state.songs.add(downloadFile.getSong());
|
state.songs.add(downloadFile.getSong());
|
||||||
}
|
}
|
||||||
state.currentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
state.currentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
||||||
|
@ -191,6 +201,9 @@ public class DownloadServiceLifecycleSupport {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deserializeDownloadQueue() {
|
private void deserializeDownloadQueue() {
|
||||||
|
new DeserializeTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
private void deserializeDownloadQueueNow() {
|
||||||
State state = FileUtil.deserialize(downloadService, FILENAME_DOWNLOADS_SER);
|
State state = FileUtil.deserialize(downloadService, FILENAME_DOWNLOADS_SER);
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -200,8 +213,6 @@ public class DownloadServiceLifecycleSupport {
|
||||||
|
|
||||||
// Work-around: Serialize again, as the restore() method creates a serialization without current playing info.
|
// Work-around: Serialize again, as the restore() method creates a serialization without current playing info.
|
||||||
serializeDownloadQueue();
|
serializeDownloadQueue();
|
||||||
|
|
||||||
downloadService.setPlayerState(PlayerState.STOPPED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleKeyEvent(KeyEvent event) {
|
private void handleKeyEvent(KeyEvent event) {
|
||||||
|
@ -211,8 +222,6 @@ public class DownloadServiceLifecycleSupport {
|
||||||
|
|
||||||
switch (event.getKeyCode()) {
|
switch (event.getKeyCode()) {
|
||||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||||
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
|
||||||
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
|
||||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||||
downloadService.togglePlayPause();
|
downloadService.togglePlayPause();
|
||||||
break;
|
break;
|
||||||
|
@ -225,7 +234,15 @@ public class DownloadServiceLifecycleSupport {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KeyEvent.KEYCODE_MEDIA_STOP:
|
case KeyEvent.KEYCODE_MEDIA_STOP:
|
||||||
downloadService.reset();
|
downloadService.stop();
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
||||||
|
if(downloadService.getPlayerState() != PlayerState.STARTED) {
|
||||||
|
downloadService.start();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
||||||
|
downloadService.pause();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -244,7 +261,7 @@ public class DownloadServiceLifecycleSupport {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case TelephonyManager.CALL_STATE_RINGING:
|
case TelephonyManager.CALL_STATE_RINGING:
|
||||||
case TelephonyManager.CALL_STATE_OFFHOOK:
|
case TelephonyManager.CALL_STATE_OFFHOOK:
|
||||||
if (downloadService.getPlayerState() == PlayerState.STARTED) {
|
if (downloadService.getPlayerState() == PlayerState.STARTED && !downloadService.isJukeboxEnabled()) {
|
||||||
resumeAfterCall = true;
|
resumeAfterCall = true;
|
||||||
downloadService.pause();
|
downloadService.pause();
|
||||||
}
|
}
|
||||||
|
@ -268,4 +285,30 @@ public class DownloadServiceLifecycleSupport {
|
||||||
private int currentPlayingIndex;
|
private int currentPlayingIndex;
|
||||||
private int currentPlayingPosition;
|
private int currentPlayingPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class SerializeTask extends AsyncTask<Void, Void, Void> {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
|
if(lock.tryLock()) {
|
||||||
|
try {
|
||||||
|
serializeDownloadQueueNow();
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private class DeserializeTask extends AsyncTask<Void, Void, Void> {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
lock.lock();
|
||||||
|
deserializeDownloadQueueNow();
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -47,26 +47,34 @@ public interface MusicService {
|
||||||
|
|
||||||
boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception;
|
boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
List<MusicFolder> getMusicFolders(Context context, ProgressListener progressListener) throws Exception;
|
|
||||||
|
|
||||||
List<Genre> getGenres(Context context, ProgressListener progressListener) throws Exception;
|
List<Genre> getGenres(Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
void star(String id, Context context, ProgressListener progressListener) throws Exception;
|
void star(String id, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
void unstar(String id, Context context, ProgressListener progressListener) throws Exception;
|
void unstar(String id, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
|
List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
MusicDirectory getMusicDirectory(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
MusicDirectory getMusicDirectory(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception;
|
SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception;
|
MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context, ProgressListener progressListener) throws Exception;
|
void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
|
void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
|
void addToPlaylist(String id, List<MusicDirectory.Entry> toAdd, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
|
void removeFromPlaylist(String id, List<Integer> toRemove, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
|
void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception;
|
Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception;
|
void scrobble(String id, boolean submission, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
@ -87,7 +95,9 @@ public interface MusicService {
|
||||||
|
|
||||||
Version getLatestVersion(Context context, ProgressListener progressListener) throws Exception;
|
Version getLatestVersion(Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
String getVideoUrl(Context context, String id);
|
String getVideoUrl(int maxBitrate, Context context, String id);
|
||||||
|
|
||||||
|
String getVideoStreamUrl(int Bitrate, Context context, String id);
|
||||||
|
|
||||||
JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception;
|
JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception;
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,15 @@
|
||||||
*/
|
*/
|
||||||
package com.thejoshwa.ultrasonic.androidapp.service;
|
package com.thejoshwa.ultrasonic.androidapp.service;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileReader;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -54,6 +58,7 @@ import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
||||||
* @author Sindre Mehus
|
* @author Sindre Mehus
|
||||||
*/
|
*/
|
||||||
public class OfflineMusicService extends RESTMusicService {
|
public class OfflineMusicService extends RESTMusicService {
|
||||||
|
private static final String TAG = OfflineMusicService.class.getSimpleName();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception {
|
public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
@ -86,14 +91,14 @@ public class OfflineMusicService extends RESTMusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MusicDirectory getMusicDirectory(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
public MusicDirectory getMusicDirectory(String id, String artistName, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||||
File dir = new File(id);
|
File dir = new File(id);
|
||||||
MusicDirectory result = new MusicDirectory();
|
MusicDirectory result = new MusicDirectory();
|
||||||
result.setName(dir.getName());
|
result.setName(dir.getName());
|
||||||
|
|
||||||
Set<String> names = new HashSet<String>();
|
Set<String> names = new HashSet<String>();
|
||||||
|
|
||||||
for (File file : FileUtil.listMusicFiles(dir)) {
|
for (File file : FileUtil.listMediaFiles(dir)) {
|
||||||
String name = getName(file);
|
String name = getName(file);
|
||||||
if (name != null & !names.contains(name)) {
|
if (name != null & !names.contains(name)) {
|
||||||
names.add(name);
|
names.add(name);
|
||||||
|
@ -253,14 +258,10 @@ public class OfflineMusicService extends RESTMusicService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception {
|
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception {
|
||||||
InputStream in = new FileInputStream(entry.getCoverArt());
|
|
||||||
try {
|
try {
|
||||||
byte[] bytes = Util.toByteArray(in);
|
return FileUtil.getAlbumArtBitmap(context, entry, size);
|
||||||
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
} catch(Exception e) {
|
||||||
Log.i("getCoverArt", "getCoverArt");
|
return null;
|
||||||
return Util.scaleBitmap(bitmap, size);
|
|
||||||
} finally {
|
|
||||||
Util.close(in);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,23 +276,184 @@ public class OfflineMusicService extends RESTMusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<MusicFolder> getMusicFolders(Context context, ProgressListener progressListener) throws Exception {
|
public List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||||
throw new OfflineException("Music folders not available in offline mode");
|
throw new OfflineException("Music folders not available in offline mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception {
|
public SearchResult search(SearchCritera criteria, Context context, ProgressListener progressListener) throws Exception {
|
||||||
throw new OfflineException("Search not available in offline mode");
|
List<Artist> artists = new ArrayList<Artist>();
|
||||||
|
List<MusicDirectory.Entry> albums = new ArrayList<MusicDirectory.Entry>();
|
||||||
|
List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>();
|
||||||
|
File root = FileUtil.getMusicDirectory(context);
|
||||||
|
int closeness = 0;
|
||||||
|
for (File artistFile : FileUtil.listFiles(root)) {
|
||||||
|
String artistName = artistFile.getName();
|
||||||
|
if (artistFile.isDirectory()) {
|
||||||
|
if((closeness = matchCriteria(criteria, artistName)) > 0) {
|
||||||
|
Artist artist = new Artist();
|
||||||
|
artist.setId(artistFile.getPath());
|
||||||
|
artist.setIndex(artistFile.getName().substring(0, 1));
|
||||||
|
artist.setName(artistName);
|
||||||
|
artist.setCloseness(closeness);
|
||||||
|
artists.add(artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
recursiveAlbumSearch(artistName, artistFile, criteria, context, albums, songs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(artists, new Comparator<Artist>() {
|
||||||
|
public int compare(Artist lhs, Artist rhs) {
|
||||||
|
if(lhs.getCloseness() == rhs.getCloseness()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if(lhs.getCloseness() > rhs.getCloseness()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Collections.sort(albums, new Comparator<MusicDirectory.Entry>() {
|
||||||
|
public int compare(MusicDirectory.Entry lhs, MusicDirectory.Entry rhs) {
|
||||||
|
if(lhs.getCloseness() == rhs.getCloseness()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if(lhs.getCloseness() > rhs.getCloseness()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Collections.sort(songs, new Comparator<MusicDirectory.Entry>() {
|
||||||
|
public int compare(MusicDirectory.Entry lhs, MusicDirectory.Entry rhs) {
|
||||||
|
if(lhs.getCloseness() == rhs.getCloseness()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if(lhs.getCloseness() > rhs.getCloseness()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return new SearchResult(artists, albums, songs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recursiveAlbumSearch(String artistName, File file, SearchCritera criteria, Context context, List<MusicDirectory.Entry> albums, List<MusicDirectory.Entry> songs) {
|
||||||
|
int closeness;
|
||||||
|
for(File albumFile : FileUtil.listMediaFiles(file)) {
|
||||||
|
if(albumFile.isDirectory()) {
|
||||||
|
String albumName = getName(albumFile);
|
||||||
|
if((closeness = matchCriteria(criteria, albumName)) > 0) {
|
||||||
|
MusicDirectory.Entry album = createEntry(context, albumFile, albumName);
|
||||||
|
album.setArtist(artistName);
|
||||||
|
album.setCloseness(closeness);
|
||||||
|
albums.add(album);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(File songFile : FileUtil.listMediaFiles(albumFile)) {
|
||||||
|
String songName = getName(songFile);
|
||||||
|
if(songFile.isDirectory()) {
|
||||||
|
recursiveAlbumSearch(artistName, songFile, criteria, context, albums, songs);
|
||||||
|
}
|
||||||
|
else if((closeness = matchCriteria(criteria, songName)) > 0){
|
||||||
|
MusicDirectory.Entry song = createEntry(context, albumFile, songName);
|
||||||
|
song.setArtist(artistName);
|
||||||
|
song.setAlbum(albumName);
|
||||||
|
song.setCloseness(closeness);
|
||||||
|
songs.add(song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String songName = getName(albumFile);
|
||||||
|
if((closeness = matchCriteria(criteria, songName)) > 0) {
|
||||||
|
MusicDirectory.Entry song = createEntry(context, albumFile, songName);
|
||||||
|
song.setArtist(artistName);
|
||||||
|
song.setAlbum(songName);
|
||||||
|
song.setCloseness(closeness);
|
||||||
|
songs.add(song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private int matchCriteria(SearchCritera criteria, String name) {
|
||||||
|
String query = criteria.getQuery().toLowerCase();
|
||||||
|
String[] queryParts = query.split(" ");
|
||||||
|
String[] nameParts = name.toLowerCase().split(" ");
|
||||||
|
|
||||||
|
int closeness = 0;
|
||||||
|
for(String queryPart : queryParts) {
|
||||||
|
for(String namePart : nameParts) {
|
||||||
|
if(namePart.equals(queryPart)) {
|
||||||
|
closeness++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return closeness;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
public List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||||
throw new OfflineException("Playlists not available in offline mode");
|
List<Playlist> playlists = new ArrayList<Playlist>();
|
||||||
|
File root = FileUtil.getPlaylistDirectory();
|
||||||
|
for (File file : FileUtil.listFiles(root)) {
|
||||||
|
if(FileUtil.isPlaylistFile(file)) {
|
||||||
|
String id = file.getName();
|
||||||
|
String filename = FileUtil.getBaseName(id);
|
||||||
|
Playlist playlist = new Playlist(id, filename);
|
||||||
|
playlists.add(playlist);
|
||||||
|
} else {
|
||||||
|
// Delete legacy playlist files
|
||||||
|
try {
|
||||||
|
file.delete();
|
||||||
|
} catch(Exception e) {
|
||||||
|
Log.w(TAG, "Failed to delete old playlist file: " + file.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return playlists;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
|
public MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception {
|
||||||
throw new OfflineException("Playlists not available in offline mode");
|
DownloadService downloadService = DownloadServiceImpl.getInstance();
|
||||||
|
if (downloadService == null) {
|
||||||
|
return new MusicDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
Reader reader = null;
|
||||||
|
BufferedReader buffer = null;
|
||||||
|
try {
|
||||||
|
File playlistFile = FileUtil.getPlaylistFile(name);
|
||||||
|
reader = new FileReader(playlistFile);
|
||||||
|
buffer = new BufferedReader(reader);
|
||||||
|
|
||||||
|
MusicDirectory playlist = new MusicDirectory();
|
||||||
|
String line = buffer.readLine();
|
||||||
|
if(!"#EXTM3U".equals(line)) return playlist;
|
||||||
|
|
||||||
|
while( (line = buffer.readLine()) != null ){
|
||||||
|
File entryFile = new File(line);
|
||||||
|
String entryName = getName(entryFile);
|
||||||
|
if(entryFile.exists() && entryName != null){
|
||||||
|
playlist.addChild(createEntry(context, entryFile, entryName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return playlist;
|
||||||
|
} finally {
|
||||||
|
Util.close(buffer);
|
||||||
|
Util.close(reader);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -299,6 +461,26 @@ public class OfflineMusicService extends RESTMusicService {
|
||||||
throw new OfflineException("Playlists not available in offline mode");
|
throw new OfflineException("Playlists not available in offline mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
throw new OfflineException("Playlists not available in offline mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addToPlaylist(String id, List<MusicDirectory.Entry> toAdd, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
throw new OfflineException("Updating playlist not available in offline mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeFromPlaylist(String id, List<Integer> toRemove, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
throw new OfflineException("Removing from playlist not available in offline mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
throw new OfflineException("Updating playlist not available in offline mode");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
|
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
|
||||||
throw new OfflineException("Lyrics not available in offline mode");
|
throw new OfflineException("Lyrics not available in offline mode");
|
||||||
|
@ -315,7 +497,12 @@ public class OfflineMusicService extends RESTMusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getVideoUrl(Context context, String id) {
|
public String getVideoUrl(int maxBitrate, Context context, String id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVideoStreamUrl(int maxBitrate, Context context, String id) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,7 +571,7 @@ public class OfflineMusicService extends RESTMusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void listFilesRecursively(File parent, List<File> children) {
|
private void listFilesRecursively(File parent, List<File> children) {
|
||||||
for (File file : FileUtil.listMusicFiles(parent)) {
|
for (File file : FileUtil.listMediaFiles(parent)) {
|
||||||
if (file.isFile()) {
|
if (file.isFile()) {
|
||||||
children.add(file);
|
children.add(file);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -18,7 +18,10 @@
|
||||||
*/
|
*/
|
||||||
package com.thejoshwa.ultrasonic.androidapp.service;
|
package com.thejoshwa.ultrasonic.androidapp.service;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
@ -174,13 +177,6 @@ public class RESTMusicService implements MusicService {
|
||||||
Reader reader = getReader(context, progressListener, "ping", null);
|
Reader reader = getReader(context, progressListener, "ping", null);
|
||||||
try {
|
try {
|
||||||
new ErrorParser(context).parse(reader);
|
new ErrorParser(context).parse(reader);
|
||||||
XmlPullParser parser;
|
|
||||||
parser = Xml.newPullParser();
|
|
||||||
parser.setInput(reader);
|
|
||||||
String version = parser.getAttributeValue(null, "version");
|
|
||||||
if (version != null) {
|
|
||||||
Util.setServerRestVersion(context, new Version(version));
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
Util.close(reader);
|
Util.close(reader);
|
||||||
}
|
}
|
||||||
|
@ -197,15 +193,91 @@ public class RESTMusicService implements MusicService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MusicFolder> getMusicFolders(Context context, ProgressListener progressListener) throws Exception {
|
public List<MusicFolder> getMusicFolders(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
List<MusicFolder> cachedMusicFolders = readCachedMusicFolders(context);
|
||||||
|
if (cachedMusicFolders != null && !refresh) {
|
||||||
|
return cachedMusicFolders;
|
||||||
|
}
|
||||||
|
|
||||||
Reader reader = getReader(context, progressListener, "getMusicFolders", null);
|
Reader reader = getReader(context, progressListener, "getMusicFolders", null);
|
||||||
try {
|
try {
|
||||||
return new MusicFoldersParser(context).parse(reader, progressListener);
|
List<MusicFolder> musicFolders = new MusicFoldersParser(context).parse(reader, progressListener);
|
||||||
|
writeCachedMusicFolders(context, musicFolders);
|
||||||
|
return musicFolders;
|
||||||
} finally {
|
} finally {
|
||||||
Util.close(reader);
|
Util.close(reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
Indexes cachedIndexes = readCachedIndexes(context, musicFolderId);
|
||||||
|
if (cachedIndexes != null && !refresh) {
|
||||||
|
return cachedIndexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
long lastModified = (cachedIndexes == null || refresh) ? 0L : cachedIndexes.getLastModified();
|
||||||
|
|
||||||
|
List<String> parameterNames = new ArrayList<String>();
|
||||||
|
List<Object> parameterValues = new ArrayList<Object>();
|
||||||
|
|
||||||
|
if(lastModified != 0L) {
|
||||||
|
parameterNames.add("ifModifiedSince");
|
||||||
|
parameterValues.add(lastModified);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (musicFolderId != null) {
|
||||||
|
parameterNames.add("musicFolderId");
|
||||||
|
parameterValues.add(musicFolderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Reader reader = getReader(context, progressListener, "getIndexes", null, parameterNames, parameterValues);
|
||||||
|
try {
|
||||||
|
Indexes indexes = new IndexesParser(context).parse(reader, progressListener);
|
||||||
|
if (indexes != null) {
|
||||||
|
writeCachedIndexes(context, indexes, musicFolderId);
|
||||||
|
return indexes;
|
||||||
|
}
|
||||||
|
if(cachedIndexes != null) {
|
||||||
|
return cachedIndexes;
|
||||||
|
} else {
|
||||||
|
return new Indexes(0, new ArrayList<com.thejoshwa.ultrasonic.androidapp.domain.Artist>(), new ArrayList<com.thejoshwa.ultrasonic.androidapp.domain.Artist>());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Util.close(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Indexes readCachedIndexes(Context context, String musicFolderId) {
|
||||||
|
String filename = getCachedIndexesFilename(context, musicFolderId);
|
||||||
|
return FileUtil.deserialize(context, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeCachedIndexes(Context context, Indexes indexes, String musicFolderId) {
|
||||||
|
String filename = getCachedIndexesFilename(context, musicFolderId);
|
||||||
|
FileUtil.serialize(context, indexes, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCachedIndexesFilename(Context context, String musicFolderId) {
|
||||||
|
String s = Util.getRestUrl(context, null) + musicFolderId;
|
||||||
|
return "indexes-" + Math.abs(s.hashCode()) + ".ser";
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<MusicFolder> readCachedMusicFolders(Context context) {
|
||||||
|
String filename = getCachedMusicFoldersFilename(context);
|
||||||
|
return FileUtil.deserialize(context, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeCachedMusicFolders(Context context, List<MusicFolder> musicFolders) {
|
||||||
|
String filename = getCachedMusicFoldersFilename(context);
|
||||||
|
FileUtil.serialize(context, new ArrayList<MusicFolder>(musicFolders), filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCachedMusicFoldersFilename(Context context) {
|
||||||
|
String s = Util.getRestUrl(context, null);
|
||||||
|
return "musicFolders-" + Math.abs(s.hashCode()) + ".ser";
|
||||||
|
}
|
||||||
|
|
||||||
public void star(String id, Context context, ProgressListener progressListener) throws Exception {
|
public void star(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||||
Reader reader = getReader(context, progressListener, "star", null, "id", id);
|
Reader reader = getReader(context, progressListener, "star", null, "id", id);
|
||||||
try {
|
try {
|
||||||
|
@ -225,54 +297,10 @@ public class RESTMusicService implements MusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Indexes getIndexes(String musicFolderId, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
public MusicDirectory getMusicDirectory(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||||
Indexes cachedIndexes = readCachedIndexes(context, musicFolderId);
|
|
||||||
long lastModified = cachedIndexes == null ? 0L : cachedIndexes.getLastModified();
|
|
||||||
|
|
||||||
List<String> parameterNames = new ArrayList<String>();
|
|
||||||
List<Object> parameterValues = new ArrayList<Object>();
|
|
||||||
|
|
||||||
parameterNames.add("ifModifiedSince");
|
|
||||||
parameterValues.add(lastModified);
|
|
||||||
|
|
||||||
if (musicFolderId != null) {
|
|
||||||
parameterNames.add("musicFolderId");
|
|
||||||
parameterValues.add(musicFolderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Reader reader = getReader(context, progressListener, "getIndexes", null, parameterNames, parameterValues);
|
|
||||||
try {
|
|
||||||
Indexes indexes = new IndexesParser(context).parse(reader, progressListener);
|
|
||||||
if (indexes != null) {
|
|
||||||
writeCachedIndexes(context, indexes, musicFolderId);
|
|
||||||
return indexes;
|
|
||||||
}
|
|
||||||
return cachedIndexes;
|
|
||||||
} finally {
|
|
||||||
Util.close(reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Indexes readCachedIndexes(Context context, String musicFolderId) {
|
|
||||||
String filename = getCachedIndexesFilename(context, musicFolderId);
|
|
||||||
return FileUtil.deserialize(context, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeCachedIndexes(Context context, Indexes indexes, String musicFolderId) {
|
|
||||||
String filename = getCachedIndexesFilename(context, musicFolderId);
|
|
||||||
FileUtil.serialize(context, indexes, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCachedIndexesFilename(Context context, String musicFolderId) {
|
|
||||||
String s = Util.getRestUrl(context, null) + musicFolderId;
|
|
||||||
return "indexes-" + Math.abs(s.hashCode()) + ".ser";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MusicDirectory getMusicDirectory(String id, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
|
||||||
Reader reader = getReader(context, progressListener, "getMusicDirectory", null, "id", id);
|
Reader reader = getReader(context, progressListener, "getMusicDirectory", null, "id", id);
|
||||||
try {
|
try {
|
||||||
return new MusicDirectoryParser(context).parse(reader, progressListener);
|
return new MusicDirectoryParser(context).parse(name, reader, progressListener);
|
||||||
} finally {
|
} finally {
|
||||||
Util.close(reader);
|
Util.close(reader);
|
||||||
}
|
}
|
||||||
|
@ -320,13 +348,36 @@ public class RESTMusicService implements MusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
|
public MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception {
|
||||||
HttpParams params = new BasicHttpParams();
|
HttpParams params = new BasicHttpParams();
|
||||||
HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_PLAYLIST);
|
HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_PLAYLIST);
|
||||||
|
|
||||||
Reader reader = getReader(context, progressListener, "getPlaylist", params, "id", id);
|
Reader reader = getReader(context, progressListener, "getPlaylist", params, "id", id);
|
||||||
try {
|
try {
|
||||||
return new PlaylistParser(context).parse(reader, progressListener);
|
MusicDirectory playlist = new PlaylistParser(context).parse(reader, progressListener);
|
||||||
|
|
||||||
|
File playlistFile = FileUtil.getPlaylistFile(name);
|
||||||
|
FileWriter fw = new FileWriter(playlistFile);
|
||||||
|
BufferedWriter bw = new BufferedWriter(fw);
|
||||||
|
try {
|
||||||
|
fw.write("#EXTM3U\n");
|
||||||
|
for (MusicDirectory.Entry e : playlist.getChildren()) {
|
||||||
|
String filePath = FileUtil.getSongFile(context, e).getAbsolutePath();
|
||||||
|
if(! new File(filePath).exists()){
|
||||||
|
String ext = FileUtil.getExtension(filePath);
|
||||||
|
String base = FileUtil.getBaseName(filePath);
|
||||||
|
filePath = base + ".complete." + ext;
|
||||||
|
}
|
||||||
|
fw.write(filePath + "\n");
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
Log.w(TAG, "Failed to save playlist: " + name);
|
||||||
|
} finally {
|
||||||
|
bw.close();
|
||||||
|
fw.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return playlist;
|
||||||
} finally {
|
} finally {
|
||||||
Util.close(reader);
|
Util.close(reader);
|
||||||
}
|
}
|
||||||
|
@ -368,6 +419,65 @@ public class RESTMusicService implements MusicService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deletePlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
Reader reader = getReader(context, progressListener, "deletePlaylist", null, "id", id);
|
||||||
|
try {
|
||||||
|
new ErrorParser(context).parse(reader);
|
||||||
|
} finally {
|
||||||
|
Util.close(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addToPlaylist(String id, List<MusicDirectory.Entry> toAdd, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
checkServerVersion(context, "1.8", "Updating playlists is not supported.");
|
||||||
|
List<String> names = new ArrayList<String>();
|
||||||
|
List<Object> values = new ArrayList<Object>();
|
||||||
|
names.add("playlistId");
|
||||||
|
values.add(id);
|
||||||
|
for(MusicDirectory.Entry song: toAdd) {
|
||||||
|
names.add("songIdToAdd");
|
||||||
|
values.add(song.getId());
|
||||||
|
}
|
||||||
|
Reader reader = getReader(context, progressListener, "updatePlaylist", null, names, values);
|
||||||
|
try {
|
||||||
|
new ErrorParser(context).parse(reader);
|
||||||
|
} finally {
|
||||||
|
Util.close(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeFromPlaylist(String id, List<Integer> toRemove, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
checkServerVersion(context, "1.8", "Updating playlists is not supported.");
|
||||||
|
List<String> names = new ArrayList<String>();
|
||||||
|
List<Object> values = new ArrayList<Object>();
|
||||||
|
names.add("playlistId");
|
||||||
|
values.add(id);
|
||||||
|
for(Integer song: toRemove) {
|
||||||
|
names.add("songIndexToRemove");
|
||||||
|
values.add(song);
|
||||||
|
}
|
||||||
|
Reader reader = getReader(context, progressListener, "updatePlaylist", null, names, values);
|
||||||
|
try {
|
||||||
|
new ErrorParser(context).parse(reader);
|
||||||
|
} finally {
|
||||||
|
Util.close(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updatePlaylist(String id, String name, String comment, boolean pub, Context context, ProgressListener progressListener) throws Exception {
|
||||||
|
checkServerVersion(context, "1.8", "Updating playlists is not supported.");
|
||||||
|
Reader reader = getReader(context, progressListener, "updatePlaylist", null, Arrays.asList("playlistId", "name", "comment", "public"), Arrays.<Object>asList(id, name, comment, pub));
|
||||||
|
try {
|
||||||
|
new ErrorParser(context).parse(reader);
|
||||||
|
} finally {
|
||||||
|
Util.close(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
|
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
|
||||||
Reader reader = getReader(context, progressListener, "getLyrics", null, Arrays.asList("artist", "title"), Arrays.<Object>asList(artist, title));
|
Reader reader = getReader(context, progressListener, "getLyrics", null, Arrays.asList("artist", "title"), Arrays.<Object>asList(artist, title));
|
||||||
|
@ -405,7 +515,13 @@ public class RESTMusicService implements MusicService {
|
||||||
HttpParams params = new BasicHttpParams();
|
HttpParams params = new BasicHttpParams();
|
||||||
HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS);
|
HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS);
|
||||||
|
|
||||||
Reader reader = getReader(context, progressListener, "getRandomSongs", params, "size", size);
|
List<String> names = new ArrayList<String>();
|
||||||
|
List<Object> values = new ArrayList<Object>();
|
||||||
|
|
||||||
|
names.add("size");
|
||||||
|
values.add(size);
|
||||||
|
|
||||||
|
Reader reader = getReader(context, progressListener, "getRandomSongs", params, names, values);
|
||||||
try {
|
try {
|
||||||
return new RandomSongsParser(context).parse(reader, progressListener);
|
return new RandomSongsParser(context).parse(reader, progressListener);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -487,6 +603,17 @@ public class RESTMusicService implements MusicService {
|
||||||
|
|
||||||
byte[] bytes = Util.toByteArray(in);
|
byte[] bytes = Util.toByteArray(in);
|
||||||
|
|
||||||
|
File albumDir = FileUtil.getAlbumDirectory(context, entry);
|
||||||
|
if (albumDir.exists()) {
|
||||||
|
OutputStream out = null;
|
||||||
|
try {
|
||||||
|
out = new FileOutputStream(FileUtil.getAlbumArtFile(albumDir));
|
||||||
|
out.write(bytes);
|
||||||
|
} finally {
|
||||||
|
Util.close(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we aren't allowing server-side scaling, always save the file to disk because it will be unmodified
|
// If we aren't allowing server-side scaling, always save the file to disk because it will be unmodified
|
||||||
if (!serverScaling || saveToFile) {
|
if (!serverScaling || saveToFile) {
|
||||||
OutputStream out = null;
|
OutputStream out = null;
|
||||||
|
@ -545,10 +672,10 @@ public class RESTMusicService implements MusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getVideoUrl(Context context, String id) {
|
public String getVideoUrl(int maxBitrate, Context context, String id) {
|
||||||
StringBuilder builder = new StringBuilder(Util.getRestUrl(context, "videoPlayer"));
|
StringBuilder builder = new StringBuilder(Util.getRestUrl(context, "videoPlayer"));
|
||||||
builder.append("&id=").append(id);
|
builder.append("&id=").append(id);
|
||||||
builder.append("&maxBitRate=500");
|
builder.append("&maxBitRate=").append(maxBitrate);
|
||||||
builder.append("&autoplay=true");
|
builder.append("&autoplay=true");
|
||||||
|
|
||||||
String url = rewriteUrlWithRedirect(context, builder.toString());
|
String url = rewriteUrlWithRedirect(context, builder.toString());
|
||||||
|
@ -556,6 +683,17 @@ public class RESTMusicService implements MusicService {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVideoStreamUrl(int maxBitrate, Context context, String id) {
|
||||||
|
StringBuilder builder = new StringBuilder(Util.getRestUrl(context, "stream"));
|
||||||
|
builder.append("&id=").append(id);
|
||||||
|
builder.append("&maxBitRate=").append(maxBitrate);
|
||||||
|
|
||||||
|
String url = rewriteUrlWithRedirect(context, builder.toString());
|
||||||
|
Log.i(TAG, "Using video URL: " + url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception {
|
public JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception {
|
||||||
int n = ids.size();
|
int n = ids.size();
|
||||||
|
@ -598,7 +736,6 @@ public class RESTMusicService implements MusicService {
|
||||||
List<String> parameterNames = Arrays.asList("action", "gain");
|
List<String> parameterNames = Arrays.asList("action", "gain");
|
||||||
List<Object> parameterValues = Arrays.<Object>asList("setGain", gain);
|
List<Object> parameterValues = Arrays.<Object>asList("setGain", gain);
|
||||||
return executeJukeboxCommand(context, progressListener, parameterNames, parameterValues);
|
return executeJukeboxCommand(context, progressListener, parameterNames, parameterValues);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private JukeboxStatus executeJukeboxCommand(Context context, ProgressListener progressListener, List<String> parameterNames, List<Object> parameterValues) throws Exception {
|
private JukeboxStatus executeJukeboxCommand(Context context, ProgressListener progressListener, List<String> parameterNames, List<Object> parameterValues) throws Exception {
|
||||||
|
@ -631,8 +768,7 @@ public class RESTMusicService implements MusicService {
|
||||||
return getReaderForURL(context, url, requestParams, parameterNames, parameterValues, progressListener);
|
return getReaderForURL(context, url, requestParams, parameterNames, parameterValues, progressListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Reader getReaderForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames,
|
private Reader getReaderForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener) throws Exception {
|
||||||
List<Object> parameterValues, ProgressListener progressListener) throws Exception {
|
|
||||||
HttpEntity entity = getEntityForURL(context, url, requestParams, parameterNames, parameterValues, progressListener);
|
HttpEntity entity = getEntityForURL(context, url, requestParams, parameterNames, parameterValues, progressListener);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
throw new RuntimeException("No entity received for URL " + url);
|
throw new RuntimeException("No entity received for URL " + url);
|
||||||
|
@ -642,18 +778,18 @@ public class RESTMusicService implements MusicService {
|
||||||
return new InputStreamReader(in, Constants.UTF_8);
|
return new InputStreamReader(in, Constants.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames,
|
private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener) throws Exception {
|
||||||
List<Object> parameterValues, ProgressListener progressListener) throws Exception {
|
return getResponseForURL(context, url, requestParams, parameterNames,
|
||||||
return getResponseForURL(context, url, requestParams, parameterNames, parameterValues, null, progressListener, null).getEntity();
|
parameterValues, null, progressListener, null).getEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpResponse getResponseForURL(Context context, String url, HttpParams requestParams,
|
private HttpResponse getResponseForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, List<Header> headers, ProgressListener progressListener, CancellableTask task) throws Exception {
|
||||||
List<String> parameterNames, List<Object> parameterValues,
|
|
||||||
List<Header> headers, ProgressListener progressListener, CancellableTask task) throws Exception {
|
|
||||||
Log.d(TAG, "Connections in pool: " + connManager.getConnectionsInPool());
|
Log.d(TAG, "Connections in pool: " + connManager.getConnectionsInPool());
|
||||||
|
|
||||||
// If not too many parameters, extract them to the URL rather than relying on the HTTP POST request being
|
// If not too many parameters, extract them to the URL rather than
|
||||||
// received intact. Remember, HTTP POST requests are converted to GET requests during HTTP redirects, thus
|
// relying on the HTTP POST request being
|
||||||
|
// received intact. Remember, HTTP POST requests are converted to GET
|
||||||
|
// requests during HTTP redirects, thus
|
||||||
// loosing its entity.
|
// loosing its entity.
|
||||||
if (parameterNames != null && parameterNames.size() < 10) {
|
if (parameterNames != null && parameterNames.size() < 10) {
|
||||||
StringBuilder builder = new StringBuilder(url);
|
StringBuilder builder = new StringBuilder(url);
|
||||||
|
@ -670,11 +806,15 @@ public class RESTMusicService implements MusicService {
|
||||||
return executeWithRetry(context, rewrittenUrl, url, requestParams, parameterNames, parameterValues, headers, progressListener, task);
|
return executeWithRetry(context, rewrittenUrl, url, requestParams, parameterNames, parameterValues, headers, progressListener, task);
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpResponse executeWithRetry(Context context, String url, String originalUrl, HttpParams requestParams,
|
private HttpResponse executeWithRetry(Context context, String url, String originalUrl, HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, List<Header> headers, ProgressListener progressListener, CancellableTask task) throws IOException {
|
||||||
List<String> parameterNames, List<Object> parameterValues,
|
|
||||||
List<Header> headers, ProgressListener progressListener, CancellableTask task) throws IOException {
|
|
||||||
Log.i(TAG, "Using URL " + url);
|
Log.i(TAG, "Using URL " + url);
|
||||||
|
|
||||||
|
SharedPreferences prefs = Util.getPreferences(context);
|
||||||
|
int networkTimeout = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, "15000"));
|
||||||
|
HttpParams newParams = httpClient.getParams();
|
||||||
|
HttpConnectionParams.setSoTimeout(newParams, networkTimeout);
|
||||||
|
httpClient.setParams(newParams);
|
||||||
|
|
||||||
final AtomicReference<Boolean> cancelled = new AtomicReference<Boolean>(false);
|
final AtomicReference<Boolean> cancelled = new AtomicReference<Boolean>(false);
|
||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -687,8 +827,16 @@ public class RESTMusicService implements MusicService {
|
||||||
task.setOnCancelListener(new CancellableTask.OnCancelListener() {
|
task.setOnCancelListener(new CancellableTask.OnCancelListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onCancel() {
|
public void onCancel() {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
cancelled.set(true);
|
cancelled.set(true);
|
||||||
request.abort();
|
request.abort();
|
||||||
|
} catch(Exception e) {
|
||||||
|
Log.e(TAG, "Failed to stop http task");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -713,7 +861,6 @@ public class RESTMusicService implements MusicService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set credentials to get through apache proxies that require authentication.
|
// Set credentials to get through apache proxies that require authentication.
|
||||||
SharedPreferences prefs = Util.getPreferences(context);
|
|
||||||
int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
|
int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
|
||||||
String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
|
String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
|
||||||
String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
|
String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
|
||||||
|
@ -721,8 +868,6 @@ public class RESTMusicService implements MusicService {
|
||||||
new UsernamePasswordCredentials(username, password));
|
new UsernamePasswordCredentials(username, password));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
httpClient.getParams().setParameter("http.socket.timeout", Util.getNetworkTimeout(context));
|
|
||||||
|
|
||||||
HttpResponse response = httpClient.execute(request, httpContext);
|
HttpResponse response = httpClient.execute(request, httpContext);
|
||||||
detectRedirect(originalUrl, context, httpContext);
|
detectRedirect(originalUrl, context, httpContext);
|
||||||
return response;
|
return response;
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class AlbumListParser extends MusicDirectoryEntryParser {
|
||||||
if (eventType == XmlPullParser.START_TAG) {
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
String name = getElementName();
|
String name = getElementName();
|
||||||
if ("album".equals(name)) {
|
if ("album".equals(name)) {
|
||||||
dir.addChild(parseEntry());
|
dir.addChild(parseEntry(""));
|
||||||
} else if ("error".equals(name)) {
|
} else if ("error".equals(name)) {
|
||||||
handleError();
|
handleError();
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ public class MusicDirectoryEntryParser extends AbstractParser {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MusicDirectory.Entry parseEntry() {
|
protected MusicDirectory.Entry parseEntry(String artist) {
|
||||||
MusicDirectory.Entry entry = new MusicDirectory.Entry();
|
MusicDirectory.Entry entry = new MusicDirectory.Entry();
|
||||||
entry.setId(get("id"));
|
entry.setId(get("id"));
|
||||||
entry.setParent(get("parent"));
|
entry.setParent(get("parent"));
|
||||||
|
@ -55,6 +55,8 @@ public class MusicDirectoryEntryParser extends AbstractParser {
|
||||||
entry.setPath(get("path"));
|
entry.setPath(get("path"));
|
||||||
entry.setVideo(getBoolean("isVideo"));
|
entry.setVideo(getBoolean("isVideo"));
|
||||||
entry.setDiscNumber(getInteger("discNumber"));
|
entry.setDiscNumber(getInteger("discNumber"));
|
||||||
|
} else if(!"".equals(artist)) {
|
||||||
|
entry.setPath(artist + "/" + entry.getTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
|
|
|
@ -24,7 +24,6 @@ import com.thejoshwa.ultrasonic.androidapp.R;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.ProgressListener;
|
import com.thejoshwa.ultrasonic.androidapp.util.ProgressListener;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,7 +37,7 @@ public class MusicDirectoryParser extends MusicDirectoryEntryParser {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MusicDirectory parse(Reader reader, ProgressListener progressListener) throws Exception {
|
public MusicDirectory parse(String artist, Reader reader, ProgressListener progressListener) throws Exception {
|
||||||
|
|
||||||
long t0 = System.currentTimeMillis();
|
long t0 = System.currentTimeMillis();
|
||||||
updateProgress(progressListener, R.string.parser_reading);
|
updateProgress(progressListener, R.string.parser_reading);
|
||||||
|
@ -51,7 +50,7 @@ public class MusicDirectoryParser extends MusicDirectoryEntryParser {
|
||||||
if (eventType == XmlPullParser.START_TAG) {
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
String name = getElementName();
|
String name = getElementName();
|
||||||
if ("child".equals(name)) {
|
if ("child".equals(name)) {
|
||||||
dir.addChild(parseEntry());
|
dir.addChild(parseEntry(artist));
|
||||||
} else if ("directory".equals(name)) {
|
} else if ("directory".equals(name)) {
|
||||||
dir.setName(get("name"));
|
dir.setName(get("name"));
|
||||||
} else if ("error".equals(name)) {
|
} else if ("error".equals(name)) {
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class PlaylistParser extends MusicDirectoryEntryParser {
|
||||||
if (eventType == XmlPullParser.START_TAG) {
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
String name = getElementName();
|
String name = getElementName();
|
||||||
if ("entry".equals(name)) {
|
if ("entry".equals(name)) {
|
||||||
dir.addChild(parseEntry());
|
dir.addChild(parseEntry(""));
|
||||||
} else if ("error".equals(name)) {
|
} else if ("error".equals(name)) {
|
||||||
handleError();
|
handleError();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ import android.content.Context;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.R;
|
import com.thejoshwa.ultrasonic.androidapp.R;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.Playlist;
|
import com.thejoshwa.ultrasonic.androidapp.domain.Playlist;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.util.ProgressListener;
|
import com.thejoshwa.ultrasonic.androidapp.util.ProgressListener;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.view.PlaylistAdapter;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
@ -51,7 +53,12 @@ public class PlaylistsParser extends AbstractParser {
|
||||||
if ("playlist".equals(tag)) {
|
if ("playlist".equals(tag)) {
|
||||||
String id = get("id");
|
String id = get("id");
|
||||||
String name = get("name");
|
String name = get("name");
|
||||||
result.add(new Playlist(id, name));
|
String owner = get("owner");
|
||||||
|
String comment = get("comment");
|
||||||
|
String songCount = get("songCount");
|
||||||
|
String created = get("created");
|
||||||
|
String pub = get("public");
|
||||||
|
result.add(new Playlist(id, name, owner, comment, songCount, created, pub));
|
||||||
} else if ("error".equals(tag)) {
|
} else if ("error".equals(tag)) {
|
||||||
handleError();
|
handleError();
|
||||||
}
|
}
|
||||||
|
@ -61,7 +68,7 @@ public class PlaylistsParser extends AbstractParser {
|
||||||
validate();
|
validate();
|
||||||
updateProgress(progressListener, R.string.parser_reading_done);
|
updateProgress(progressListener, R.string.parser_reading_done);
|
||||||
|
|
||||||
return result;
|
return PlaylistAdapter.PlaylistComparator.sort(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -46,7 +46,7 @@ public class RandomSongsParser extends MusicDirectoryEntryParser {
|
||||||
if (eventType == XmlPullParser.START_TAG) {
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
String name = getElementName();
|
String name = getElementName();
|
||||||
if ("song".equals(name)) {
|
if ("song".equals(name)) {
|
||||||
dir.addChild(parseEntry());
|
dir.addChild(parseEntry(""));
|
||||||
} else if ("error".equals(name)) {
|
} else if ("error".equals(name)) {
|
||||||
handleError();
|
handleError();
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,9 +57,9 @@ public class SearchResult2Parser extends MusicDirectoryEntryParser {
|
||||||
artist.setName(get("name"));
|
artist.setName(get("name"));
|
||||||
artists.add(artist);
|
artists.add(artist);
|
||||||
} else if ("album".equals(name)) {
|
} else if ("album".equals(name)) {
|
||||||
albums.add(parseEntry());
|
albums.add(parseEntry(""));
|
||||||
} else if ("song".equals(name)) {
|
} else if ("song".equals(name)) {
|
||||||
songs.add(parseEntry());
|
songs.add(parseEntry(""));
|
||||||
} else if ("error".equals(name)) {
|
} else if ("error".equals(name)) {
|
||||||
handleError();
|
handleError();
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ public class SearchResultParser extends MusicDirectoryEntryParser {
|
||||||
if (eventType == XmlPullParser.START_TAG) {
|
if (eventType == XmlPullParser.START_TAG) {
|
||||||
String name = getElementName();
|
String name = getElementName();
|
||||||
if ("match".equals(name)) {
|
if ("match".equals(name)) {
|
||||||
songs.add(parseEntry());
|
songs.add(parseEntry(""));
|
||||||
} else if ("error".equals(name)) {
|
} else if ("error".equals(name)) {
|
||||||
handleError();
|
handleError();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
package com.thejoshwa.ultrasonic.androidapp.util;
|
package com.thejoshwa.ultrasonic.androidapp.util;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.StatFs;
|
import android.os.StatFs;
|
||||||
|
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.domain.Playlist;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadService;
|
import com.thejoshwa.ultrasonic.androidapp.service.DownloadService;
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ import com.thejoshwa.ultrasonic.androidapp.service.DownloadService;
|
||||||
public class CacheCleaner {
|
public class CacheCleaner {
|
||||||
|
|
||||||
private static final String TAG = CacheCleaner.class.getSimpleName();
|
private static final String TAG = CacheCleaner.class.getSimpleName();
|
||||||
private static final double MAX_FILE_SYSTEM_USAGE = 0.95;
|
private static final long MIN_FREE_SPACE = 500 * 1024L * 1024L;
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final DownloadService downloadService;
|
private final DownloadService downloadService;
|
||||||
|
@ -35,33 +36,15 @@ public class CacheCleaner {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clean() {
|
public void clean() {
|
||||||
new Thread(new Runnable() {
|
new BackgroundCleanup().execute();
|
||||||
public void run() {
|
|
||||||
Log.i(TAG, "Starting cache cleaning.");
|
|
||||||
|
|
||||||
if (downloadService == null) {
|
|
||||||
Log.e(TAG, "DownloadService not set. Aborting cache cleaning.");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
public void cleanSpace() {
|
||||||
List<File> files = new ArrayList<File>();
|
new BackgroundSpaceCleanup().execute();
|
||||||
List<File> dirs = new ArrayList<File>();
|
|
||||||
|
|
||||||
findCandidatesForDeletion(FileUtil.getMusicDirectory(context), files, dirs);
|
|
||||||
sortByAscendingModificationTime(files);
|
|
||||||
|
|
||||||
Set<File> undeletable = findUndeletableFiles();
|
|
||||||
|
|
||||||
deleteFiles(files, undeletable);
|
|
||||||
deleteEmptyDirs(dirs, undeletable);
|
|
||||||
Log.i(TAG, "Completed cache cleaning.");
|
|
||||||
|
|
||||||
} catch (RuntimeException x) {
|
|
||||||
Log.e(TAG, "Error in cache cleaning.", x);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}).start();
|
public void cleanPlaylists(List<Playlist> playlists) {
|
||||||
|
new BackgroundPlaylistsCleanup().execute(playlists);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteEmptyDirs(List<File> dirs, Set<File> undeletable) {
|
private void deleteEmptyDirs(List<File> dirs, Set<File> undeletable) {
|
||||||
|
@ -72,58 +55,61 @@ public class CacheCleaner {
|
||||||
|
|
||||||
File[] children = dir.listFiles();
|
File[] children = dir.listFiles();
|
||||||
|
|
||||||
// Delete empty directory and associated album artwork.
|
// No songs left in the folder
|
||||||
if (children != null && children.length == 0) {
|
if(children.length == 1 && children[0].getPath().equals(FileUtil.getAlbumArtFile(dir).getPath())) {
|
||||||
Util.delete(dir);
|
|
||||||
Util.delete(FileUtil.getAlbumArtFile(dir));
|
Util.delete(FileUtil.getAlbumArtFile(dir));
|
||||||
|
children = dir.listFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete empty directory
|
||||||
|
if (children.length == 0) {
|
||||||
|
Util.delete(dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteFiles(List<File> files, Set<File> undeletable) {
|
private long getMinimumDelete(List<File> files) {
|
||||||
|
if(files.size() == 0) {
|
||||||
if (files.isEmpty()) {
|
return 0L;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
long cacheSizeBytes = Util.getCacheSizeMB(context) * 1024L * 1024L;
|
long cacheSizeBytes = Util.getCacheSizeMB(context) * 1024L * 1024L;
|
||||||
|
|
||||||
long bytesUsedByUltraSonic = 0L;
|
long bytesUsedBySubsonic = 0L;
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
bytesUsedByUltraSonic += file.length();
|
bytesUsedBySubsonic += file.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
long bytesToDelete = 0;
|
|
||||||
|
|
||||||
// Ensure that file system is not more than 95% full.
|
// Ensure that file system is not more than 95% full.
|
||||||
try
|
|
||||||
{
|
|
||||||
StatFs stat = new StatFs(files.get(0).getPath());
|
StatFs stat = new StatFs(files.get(0).getPath());
|
||||||
long bytesTotalFs = (long) stat.getBlockCount() * (long) stat.getBlockSize();
|
long bytesTotalFs = (long) stat.getBlockCount() * (long) stat.getBlockSize();
|
||||||
long bytesAvailableFs = (long) stat.getAvailableBlocks() * (long) stat.getBlockSize();
|
long bytesAvailableFs = (long) stat.getAvailableBlocks() * (long) stat.getBlockSize();
|
||||||
long bytesUsedFs = bytesTotalFs - bytesAvailableFs;
|
long bytesUsedFs = bytesTotalFs - bytesAvailableFs;
|
||||||
long minFsAvailability = Math.round(MAX_FILE_SYSTEM_USAGE * (double) bytesTotalFs);
|
long minFsAvailability = bytesTotalFs - MIN_FREE_SPACE;
|
||||||
|
|
||||||
long bytesToDeleteCacheLimit = Math.max(bytesUsedByUltraSonic - cacheSizeBytes, 0L);
|
long bytesToDeleteCacheLimit = Math.max(bytesUsedBySubsonic - cacheSizeBytes, 0L);
|
||||||
long bytesToDeleteFsLimit = Math.max(bytesUsedFs - minFsAvailability, 0L);
|
long bytesToDeleteFsLimit = Math.max(bytesUsedFs - minFsAvailability, 0L);
|
||||||
bytesToDelete = Math.max(bytesToDeleteCacheLimit, bytesToDeleteFsLimit);
|
long bytesToDelete = Math.max(bytesToDeleteCacheLimit, bytesToDeleteFsLimit);
|
||||||
|
|
||||||
Log.i(TAG, "File system : " + Util.formatBytes(bytesAvailableFs) + " of " + Util.formatBytes(bytesTotalFs) + " available");
|
Log.i(TAG, "File system : " + Util.formatBytes(bytesAvailableFs) + " of " + Util.formatBytes(bytesTotalFs) + " available");
|
||||||
Log.i(TAG, "Cache limit : " + Util.formatBytes(cacheSizeBytes));
|
Log.i(TAG, "Cache limit : " + Util.formatBytes(cacheSizeBytes));
|
||||||
Log.i(TAG, "Cache size before : " + Util.formatBytes(bytesUsedByUltraSonic));
|
Log.i(TAG, "Cache size before : " + Util.formatBytes(bytesUsedBySubsonic));
|
||||||
Log.i(TAG, "Minimum to delete : " + Util.formatBytes(bytesToDelete));
|
Log.i(TAG, "Minimum to delete : " + Util.formatBytes(bytesToDelete));
|
||||||
} catch (Exception x) {
|
|
||||||
//
|
return bytesToDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteFiles(List<File> files, Set<File> undeletable, long bytesToDelete, boolean deletePartials) {
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long bytesDeleted = 0L;
|
long bytesDeleted = 0L;
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
if (file.getName().equals(Constants.ALBUM_ART_FILE)) {
|
if(!deletePartials && bytesDeleted > bytesToDelete) break;
|
||||||
// Move artwork to new folder.
|
|
||||||
file.renameTo(FileUtil.getAlbumArtFile(file.getParentFile()));
|
|
||||||
|
|
||||||
} else if (bytesToDelete > bytesDeleted || file.getName().endsWith(".partial") || file.getName().contains(".partial.")) {
|
if (bytesToDelete > bytesDeleted || (deletePartials && (file.getName().endsWith(".partial") || file.getName().contains(".partial.")))) {
|
||||||
if (!undeletable.contains(file)) {
|
if (!undeletable.contains(file) && !file.getName().equals(Constants.ALBUM_ART_FILE)) {
|
||||||
long size = file.length();
|
long size = file.length();
|
||||||
if (Util.delete(file)) {
|
if (Util.delete(file)) {
|
||||||
bytesDeleted += size;
|
bytesDeleted += size;
|
||||||
|
@ -133,15 +119,13 @@ public class CacheCleaner {
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i(TAG, "Deleted : " + Util.formatBytes(bytesDeleted));
|
Log.i(TAG, "Deleted : " + Util.formatBytes(bytesDeleted));
|
||||||
Log.i(TAG, "Cache size after : " + Util.formatBytes(bytesUsedByUltraSonic - bytesDeleted));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void findCandidatesForDeletion(File file, List<File> files, List<File> dirs) {
|
private void findCandidatesForDeletion(File file, List<File> files, List<File> dirs) {
|
||||||
if (file.isFile()) {
|
if (file.isFile()) {
|
||||||
String name = file.getName();
|
String name = file.getName();
|
||||||
boolean isCacheFile = name.endsWith(".partial") || name.contains(".partial.") || name.endsWith(".complete") || name.contains(".complete.");
|
boolean isCacheFile = name.endsWith(".partial") || name.contains(".partial.") || name.endsWith(".complete") || name.contains(".complete.");
|
||||||
boolean isAlbumArtFile = name.equals(Constants.ALBUM_ART_FILE);
|
if (isCacheFile) {
|
||||||
if (isCacheFile || isAlbumArtFile) {
|
|
||||||
files.add(file);
|
files.add(file);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -179,4 +163,79 @@ public class CacheCleaner {
|
||||||
undeletable.add(FileUtil.getMusicDirectory(context));
|
undeletable.add(FileUtil.getMusicDirectory(context));
|
||||||
return undeletable;
|
return undeletable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class BackgroundCleanup extends AsyncTask<Void, Void, Void> {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
|
if (downloadService == null) {
|
||||||
|
Log.e(TAG, "DownloadService not set. Aborting cache cleaning.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<File> files = new ArrayList<File>();
|
||||||
|
List<File> dirs = new ArrayList<File>();
|
||||||
|
|
||||||
|
findCandidatesForDeletion(FileUtil.getMusicDirectory(context), files, dirs);
|
||||||
|
sortByAscendingModificationTime(files);
|
||||||
|
|
||||||
|
Set<File> undeletable = findUndeletableFiles();
|
||||||
|
|
||||||
|
deleteFiles(files, undeletable, getMinimumDelete(files), true);
|
||||||
|
deleteEmptyDirs(dirs, undeletable);
|
||||||
|
} catch (RuntimeException x) {
|
||||||
|
Log.e(TAG, "Error in cache cleaning.", x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BackgroundSpaceCleanup extends AsyncTask<Void, Void, Void> {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
|
if (downloadService == null) {
|
||||||
|
Log.e(TAG, "DownloadService not set. Aborting cache cleaning.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<File> files = new ArrayList<File>();
|
||||||
|
List<File> dirs = new ArrayList<File>();
|
||||||
|
findCandidatesForDeletion(FileUtil.getMusicDirectory(context), files, dirs);
|
||||||
|
|
||||||
|
long bytesToDelete = getMinimumDelete(files);
|
||||||
|
if(bytesToDelete > 0L) {
|
||||||
|
sortByAscendingModificationTime(files);
|
||||||
|
Set<File> undeletable = findUndeletableFiles();
|
||||||
|
deleteFiles(files, undeletable, bytesToDelete, false);
|
||||||
|
}
|
||||||
|
} catch (RuntimeException x) {
|
||||||
|
Log.e(TAG, "Error in cache cleaning.", x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BackgroundPlaylistsCleanup extends AsyncTask<List<Playlist>, Void, Void> {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(List<Playlist>... params) {
|
||||||
|
try {
|
||||||
|
SortedSet<File> playlistFiles = FileUtil.listFiles(FileUtil.getPlaylistDirectory());
|
||||||
|
List<Playlist> playlists = params[0];
|
||||||
|
for (Playlist playlist : playlists) {
|
||||||
|
playlistFiles.remove(FileUtil.getPlaylistFile(playlist.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(File playlist : playlistFiles) {
|
||||||
|
playlist.delete();
|
||||||
|
}
|
||||||
|
} catch (RuntimeException x) {
|
||||||
|
Log.e(TAG, "Error in playlist cache cleaning.", x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -37,7 +37,7 @@ public abstract class CancellableTask {
|
||||||
private final AtomicReference<OnCancelListener> cancelListener = new AtomicReference<OnCancelListener>();
|
private final AtomicReference<OnCancelListener> cancelListener = new AtomicReference<OnCancelListener>();
|
||||||
|
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
Log.d(TAG, "Cancelling " + CancellableTask.this);
|
Log.i(TAG, "Cancelling " + CancellableTask.this);
|
||||||
cancelled.set(true);
|
cancelled.set(true);
|
||||||
|
|
||||||
OnCancelListener listener = cancelListener.get();
|
OnCancelListener listener = cancelListener.get();
|
||||||
|
@ -69,12 +69,12 @@ public abstract class CancellableTask {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
running.set(true);
|
running.set(true);
|
||||||
Log.d(TAG, "Starting thread for " + CancellableTask.this);
|
Log.i(TAG, "Starting thread for " + CancellableTask.this);
|
||||||
try {
|
try {
|
||||||
execute();
|
execute();
|
||||||
} finally {
|
} finally {
|
||||||
running.set(false);
|
running.set(false);
|
||||||
Log.d(TAG, "Stopping thread for " + CancellableTask.this);
|
Log.i(TAG, "Stopping thread for " + CancellableTask.this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -78,6 +78,8 @@ public final class Constants {
|
||||||
public static final String PREFERENCES_KEY_SHOW_TRACK_NUMBER = "showTrackNumber";
|
public static final String PREFERENCES_KEY_SHOW_TRACK_NUMBER = "showTrackNumber";
|
||||||
public static final String PREFERENCES_KEY_MAX_BITRATE_WIFI = "maxBitrateWifi";
|
public static final String PREFERENCES_KEY_MAX_BITRATE_WIFI = "maxBitrateWifi";
|
||||||
public static final String PREFERENCES_KEY_MAX_BITRATE_MOBILE = "maxBitrateMobile";
|
public static final String PREFERENCES_KEY_MAX_BITRATE_MOBILE = "maxBitrateMobile";
|
||||||
|
public static final String PREFERENCES_KEY_MAX_VIDEO_BITRATE_WIFI = "maxVideoBitrateWifi";
|
||||||
|
public static final String PREFERENCES_KEY_MAX_VIDEO_BITRATE_MOBILE = "maxVideoBitrateMobile";
|
||||||
public static final String PREFERENCES_KEY_CACHE_SIZE = "cacheSize";
|
public static final String PREFERENCES_KEY_CACHE_SIZE = "cacheSize";
|
||||||
public static final String PREFERENCES_KEY_CACHE_LOCATION = "cacheLocation";
|
public static final String PREFERENCES_KEY_CACHE_LOCATION = "cacheLocation";
|
||||||
public static final String PREFERENCES_KEY_PRELOAD_COUNT = "preloadCount";
|
public static final String PREFERENCES_KEY_PRELOAD_COUNT = "preloadCount";
|
||||||
|
@ -88,6 +90,9 @@ public final class Constants {
|
||||||
public static final String PREFERENCES_KEY_SERVER_SCALING = "serverScaling";
|
public static final String PREFERENCES_KEY_SERVER_SCALING = "serverScaling";
|
||||||
public static final String PREFERENCES_KEY_REPEAT_MODE = "repeatMode";
|
public static final String PREFERENCES_KEY_REPEAT_MODE = "repeatMode";
|
||||||
public static final String PREFERENCES_KEY_WIFI_REQUIRED_FOR_DOWNLOAD = "wifiRequiredForDownload";
|
public static final String PREFERENCES_KEY_WIFI_REQUIRED_FOR_DOWNLOAD = "wifiRequiredForDownload";
|
||||||
|
public static final String PREFERENCES_KEY_SHUFFLE_START_YEAR = "startYear";
|
||||||
|
public static final String PREFERENCES_KEY_SHUFFLE_END_YEAR = "endYear";
|
||||||
|
public static final String PREFERENCES_KEY_SHUFFLE_GENRE = "genre";
|
||||||
public static final String PREFERENCES_KEY_BUFFER_LENGTH = "bufferLength";
|
public static final String PREFERENCES_KEY_BUFFER_LENGTH = "bufferLength";
|
||||||
public static final String PREFERENCES_KEY_NETWORK_TIMEOUT = "networkTimeout";
|
public static final String PREFERENCES_KEY_NETWORK_TIMEOUT = "networkTimeout";
|
||||||
public static final String PREFERENCES_KEY_SHOW_NOTIFICATION = "showNotification";
|
public static final String PREFERENCES_KEY_SHOW_NOTIFICATION = "showNotification";
|
||||||
|
@ -101,8 +106,12 @@ public final class Constants {
|
||||||
public static final String PREFERENCES_KEY_DEFAULT_ARTISTS = "defaultArtists";
|
public static final String PREFERENCES_KEY_DEFAULT_ARTISTS = "defaultArtists";
|
||||||
public static final String PREFERENCES_KEY_USE_STREAM_PROXY = "useStreamProxy";
|
public static final String PREFERENCES_KEY_USE_STREAM_PROXY = "useStreamProxy";
|
||||||
public static final String PREFERENCES_KEY_SHOW_NOW_PLAYING = "showNowPlaying";
|
public static final String PREFERENCES_KEY_SHOW_NOW_PLAYING = "showNowPlaying";
|
||||||
|
public static final String PREFERENCES_KEY_GAPLESS_PLAYBACK = "gaplessPlayback";
|
||||||
public static final String PREFERENCES_KEY_CLEAR_SEARCH_HISTORY = "clearSearchHistory";
|
public static final String PREFERENCES_KEY_CLEAR_SEARCH_HISTORY = "clearSearchHistory";
|
||||||
public static final String PREFERENCES_KEY_TEST_CONNECTION = "testConnection";
|
public static final String PREFERENCES_KEY_TEST_CONNECTION = "testConnection";
|
||||||
|
public static final String PREFERENCES_EQUALIZER_ON = "equalizerOn";
|
||||||
|
public static final String PREFERENCES_EQUALIZER_SETTINGS = "equalizerSettings";
|
||||||
|
public static final String PREFERENCES_KEY_DOWNLOAD_TRANSITION = "transitionToDownloadOnPlay";
|
||||||
|
|
||||||
// Name of the preferences file.
|
// Name of the preferences file.
|
||||||
public static final String PREFERENCES_FILE_NAME = "com.thejoshwa.ultrasonic.androidapp_preferences";
|
public static final String PREFERENCES_FILE_NAME = "com.thejoshwa.ultrasonic.androidapp_preferences";
|
||||||
|
|
|
@ -36,6 +36,8 @@ import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.domain.Artist;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,11 +46,32 @@ import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||||
public class FileUtil {
|
public class FileUtil {
|
||||||
|
|
||||||
private static final String TAG = FileUtil.class.getSimpleName();
|
private static final String TAG = FileUtil.class.getSimpleName();
|
||||||
private static final String[] FILE_SYSTEM_UNSAFE = {"/", "\\", "..", ":", "\"", "?", "*", "<", ">"};
|
private static final String[] FILE_SYSTEM_UNSAFE = {"/", "\\", "..", ":", "\"", "?", "*", "<", ">", "|"};
|
||||||
private static final String[] FILE_SYSTEM_UNSAFE_DIR = {"\\", "..", ":", "\"", "?", "*", "<", ">"};
|
private static final String[] FILE_SYSTEM_UNSAFE_DIR = {"\\", "..", ":", "\"", "?", "*", "<", ">", "|"};
|
||||||
private static final List<String> MUSIC_FILE_EXTENSIONS = Arrays.asList("mp3", "ogg", "aac", "flac", "m4a", "wav", "wma");
|
private static final List<String> MUSIC_FILE_EXTENSIONS = Arrays.asList("mp3", "ogg", "aac", "flac", "m4a", "wav", "wma");
|
||||||
|
private static final List<String> VIDEO_FILE_EXTENSIONS = Arrays.asList("flv", "mp4", "m4v", "wmv", "avi", "mov", "mpg", "mkv");
|
||||||
|
private static final List<String> PLAYLIST_FILE_EXTENSIONS = Arrays.asList("m3u");
|
||||||
private static final File DEFAULT_MUSIC_DIR = createDirectory("music");
|
private static final File DEFAULT_MUSIC_DIR = createDirectory("music");
|
||||||
|
|
||||||
|
public static File getAnySong(Context context) {
|
||||||
|
File dir = getMusicDirectory(context);
|
||||||
|
return getAnySong(context, dir);
|
||||||
|
}
|
||||||
|
private static File getAnySong(Context context, File dir) {
|
||||||
|
for(File file: dir.listFiles()) {
|
||||||
|
if(file.isDirectory()) {
|
||||||
|
return getAnySong(context, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
String extension = getExtension(file.getName());
|
||||||
|
if(MUSIC_FILE_EXTENSIONS.contains(extension)) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static File getSongFile(Context context, MusicDirectory.Entry song) {
|
public static File getSongFile(Context context, MusicDirectory.Entry song) {
|
||||||
File dir = getAlbumDirectory(context, song);
|
File dir = getAlbumDirectory(context, song);
|
||||||
|
|
||||||
|
@ -72,6 +95,22 @@ public class FileUtil {
|
||||||
return new File(dir, fileName.toString());
|
return new File(dir, fileName.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static File getPlaylistFile(String name) {
|
||||||
|
File playlistDir = getPlaylistDirectory();
|
||||||
|
return new File(playlistDir, fileSystemSafe(name) + ".m3u");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getOldPlaylistFile(String name) {
|
||||||
|
File playlistDir = getPlaylistDirectory();
|
||||||
|
return new File(playlistDir, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getPlaylistDirectory() {
|
||||||
|
File playlistDir = new File(getUltraSonicDirectory(), "playlists");
|
||||||
|
ensureDirectoryExistsAndIsReadWritable(playlistDir);
|
||||||
|
return playlistDir;
|
||||||
|
}
|
||||||
|
|
||||||
public static File getAlbumArtFile(Context context, MusicDirectory.Entry entry) {
|
public static File getAlbumArtFile(Context context, MusicDirectory.Entry entry) {
|
||||||
File albumDir = getAlbumDirectory(context, entry);
|
File albumDir = getAlbumDirectory(context, entry);
|
||||||
return getAlbumArtFile(albumDir);
|
return getAlbumArtFile(albumDir);
|
||||||
|
@ -103,6 +142,11 @@ public class FileUtil {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static File getArtistDirectory(Context context, Artist artist) {
|
||||||
|
File dir = new File(getMusicDirectory(context).getPath() + "/" + fileSystemSafe(artist.getName()));
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
public static File getAlbumArtDirectory() {
|
public static File getAlbumArtDirectory() {
|
||||||
File albumArtDir = new File(getUltraSonicDirectory(), "artwork");
|
File albumArtDir = new File(getUltraSonicDirectory(), "artwork");
|
||||||
ensureDirectoryExistsAndIsReadWritable(albumArtDir);
|
ensureDirectoryExistsAndIsReadWritable(albumArtDir);
|
||||||
|
@ -110,7 +154,7 @@ public class FileUtil {
|
||||||
return albumArtDir;
|
return albumArtDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getAlbumDirectory(Context context, MusicDirectory.Entry entry) {
|
public static File getAlbumDirectory(Context context, MusicDirectory.Entry entry) {
|
||||||
File dir;
|
File dir;
|
||||||
if (entry.getPath() != null) {
|
if (entry.getPath() != null) {
|
||||||
File f = new File(fileSystemSafeDir(entry.getPath()));
|
File f = new File(fileSystemSafeDir(entry.getPath()));
|
||||||
|
@ -118,7 +162,7 @@ public class FileUtil {
|
||||||
} else {
|
} else {
|
||||||
String artist = fileSystemSafe(entry.getArtist());
|
String artist = fileSystemSafe(entry.getArtist());
|
||||||
String album = fileSystemSafe(entry.getAlbum());
|
String album = fileSystemSafe(entry.getAlbum());
|
||||||
if (album == "unnamed") {
|
if("unnamed".equals(album)) {
|
||||||
album = fileSystemSafe(entry.getTitle());
|
album = fileSystemSafe(entry.getTitle());
|
||||||
}
|
}
|
||||||
dir = new File(getMusicDirectory(context).getPath() + "/" + artist + "/" + album);
|
dir = new File(getMusicDirectory(context).getPath() + "/" + artist + "/" + album);
|
||||||
|
@ -238,23 +282,38 @@ public class FileUtil {
|
||||||
return new TreeSet<File>(Arrays.asList(files));
|
return new TreeSet<File>(Arrays.asList(files));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SortedSet<File> listMusicFiles(File dir) {
|
public static SortedSet<File> listMediaFiles(File dir) {
|
||||||
SortedSet<File> files = listFiles(dir);
|
SortedSet<File> files = listFiles(dir);
|
||||||
Iterator<File> iterator = files.iterator();
|
Iterator<File> iterator = files.iterator();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
File file = iterator.next();
|
File file = iterator.next();
|
||||||
if (!file.isDirectory() && !isMusicFile(file)) {
|
if (!file.isDirectory() && !isMediaFile(file)) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isMusicFile(File file) {
|
private static boolean isMediaFile(File file) {
|
||||||
|
String extension = getExtension(file.getName());
|
||||||
|
return MUSIC_FILE_EXTENSIONS.contains(extension) || VIDEO_FILE_EXTENSIONS.contains(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isMusicFile(File file) {
|
||||||
String extension = getExtension(file.getName());
|
String extension = getExtension(file.getName());
|
||||||
return MUSIC_FILE_EXTENSIONS.contains(extension);
|
return MUSIC_FILE_EXTENSIONS.contains(extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isVideoFile(File file) {
|
||||||
|
String extension = getExtension(file.getName());
|
||||||
|
return VIDEO_FILE_EXTENSIONS.contains(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPlaylistFile(File file) {
|
||||||
|
String extension = getExtension(file.getName());
|
||||||
|
return PLAYLIST_FILE_EXTENSIONS.contains(extension);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the extension (the substring after the last dot) of the given file. The dot
|
* Returns the extension (the substring after the last dot) of the given file. The dot
|
||||||
* is not included in the returned extension.
|
* is not included in the returned extension.
|
||||||
|
@ -264,7 +323,7 @@ public class FileUtil {
|
||||||
*/
|
*/
|
||||||
public static String getExtension(String name) {
|
public static String getExtension(String name) {
|
||||||
int index = name.lastIndexOf('.');
|
int index = name.lastIndexOf('.');
|
||||||
return index == -1 ? "" : name.substring(index + 1).toLowerCase(Locale.getDefault());
|
return index == -1 ? "" : name.substring(index + 1).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -279,6 +338,19 @@ public class FileUtil {
|
||||||
return index == -1 ? name : name.substring(0, index);
|
return index == -1 ? name : name.substring(0, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long getUsedSize(Context context, File file) {
|
||||||
|
long size = 0L;
|
||||||
|
|
||||||
|
if(file.isFile()) {
|
||||||
|
return file.length();
|
||||||
|
} else {
|
||||||
|
for (File child : FileUtil.listFiles(file)) {
|
||||||
|
size += getUsedSize(context, child);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static <T extends Serializable> boolean serialize(Context context, T obj, String fileName) {
|
public static <T extends Serializable> boolean serialize(Context context, T obj, String fileName) {
|
||||||
File file = new File(context.getCacheDir(), fileName);
|
File file = new File(context.getCacheDir(), fileName);
|
||||||
ObjectOutputStream out = null;
|
ObjectOutputStream out = null;
|
||||||
|
@ -304,7 +376,7 @@ public class FileUtil {
|
||||||
ObjectInputStream in = null;
|
ObjectInputStream in = null;
|
||||||
try {
|
try {
|
||||||
in = new ObjectInputStream(new FileInputStream(file));
|
in = new ObjectInputStream(new FileInputStream(file));
|
||||||
T result = (T)in.readObject();
|
T result = (T) in.readObject();
|
||||||
Log.i(TAG, "Deserialized object from " + file);
|
Log.i(TAG, "Deserialized object from " + file);
|
||||||
return result;
|
return result;
|
||||||
} catch (Throwable x) {
|
} catch (Throwable x) {
|
||||||
|
|
|
@ -22,6 +22,8 @@ import java.lang.ref.SoftReference;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Sindre Mehus
|
* @author Sindre Mehus
|
||||||
*/
|
*/
|
||||||
|
@ -99,4 +101,15 @@ public class LRUCache<K,V>{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected int sizeOf(String key, Drawable drawable) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void entryRemoved(boolean evicted, String key,
|
||||||
|
Drawable oldDrawable, Drawable newDrawable) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package com.thejoshwa.ultrasonic.androidapp.util;
|
||||||
|
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.activity.SubsonicTabActivity;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sindre Mehus
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public abstract class LoadingTask<T> extends BackgroundTask<T> {
|
||||||
|
|
||||||
|
private final SubsonicTabActivity tabActivity;
|
||||||
|
private final boolean cancellable;
|
||||||
|
private boolean cancelled = false;
|
||||||
|
|
||||||
|
public LoadingTask(SubsonicTabActivity activity, final boolean cancellable) {
|
||||||
|
super(activity);
|
||||||
|
tabActivity = activity;
|
||||||
|
this.cancellable = cancellable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
final ProgressDialog loading = ProgressDialog.show(tabActivity, "", "Loading. Please Wait...", true, cancellable, new DialogInterface.OnCancelListener() {
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final T result = doInBackground();
|
||||||
|
if (isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHandler().post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
loading.cancel();
|
||||||
|
done(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (final Throwable t) {
|
||||||
|
if (isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHandler().post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
loading.cancel();
|
||||||
|
error(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
private boolean isCancelled() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 17) {
|
||||||
|
return tabActivity.isDestroyed() || cancelled;
|
||||||
|
} else {
|
||||||
|
return cancelled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateProgress(final String message) {
|
||||||
|
getHandler().post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -105,5 +105,4 @@ public class ShufflePlayBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -18,12 +18,13 @@ import java.net.URLDecoder;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
import org.apache.http.Header;
|
|
||||||
import org.apache.http.HttpRequest;
|
import org.apache.http.HttpRequest;
|
||||||
import org.apache.http.message.BasicHttpRequest;
|
import org.apache.http.message.BasicHttpRequest;
|
||||||
|
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadService;
|
import com.thejoshwa.ultrasonic.androidapp.service.DownloadService;
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -35,26 +36,22 @@ public class StreamProxy implements Runnable {
|
||||||
private boolean isRunning;
|
private boolean isRunning;
|
||||||
private ServerSocket socket;
|
private ServerSocket socket;
|
||||||
private int port;
|
private int port;
|
||||||
private DownloadFile downloadFile;
|
private DownloadService downloadService;
|
||||||
|
|
||||||
public StreamProxy() {
|
public StreamProxy(DownloadService downloadService) {
|
||||||
|
|
||||||
// Create listening socket
|
// Create listening socket
|
||||||
try {
|
try {
|
||||||
socket = new ServerSocket(0, 0, InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }));
|
socket = new ServerSocket(0, 0, InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }));
|
||||||
socket.setSoTimeout(5000);
|
socket.setSoTimeout(5000);
|
||||||
port = socket.getLocalPort();
|
port = socket.getLocalPort();
|
||||||
|
this.downloadService = downloadService;
|
||||||
} catch (UnknownHostException e) { // impossible
|
} catch (UnknownHostException e) { // impossible
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "IOException initializing server", e);
|
Log.e(TAG, "IOException initializing server", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDownloadFile(DownloadFile downloadFile) {
|
|
||||||
this.downloadFile = downloadFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getPort() {
|
public int getPort() {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
@ -67,45 +64,36 @@ public class StreamProxy implements Runnable {
|
||||||
public void stop() {
|
public void stop() {
|
||||||
isRunning = false;
|
isRunning = false;
|
||||||
thread.interrupt();
|
thread.interrupt();
|
||||||
try {
|
|
||||||
thread.join(5000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Looper.prepare();
|
|
||||||
isRunning = true;
|
isRunning = true;
|
||||||
|
|
||||||
while (isRunning) {
|
while (isRunning) {
|
||||||
try {
|
try {
|
||||||
Socket client = socket.accept();
|
Socket client = socket.accept();
|
||||||
|
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
Log.i(TAG, "client connected");
|
||||||
Log.d(TAG, "client connected");
|
|
||||||
|
|
||||||
StreamToMediaPlayerTask task = new StreamToMediaPlayerTask(client);
|
StreamToMediaPlayerTask task = new StreamToMediaPlayerTask(client);
|
||||||
|
|
||||||
if (task.processRequest()) {
|
if (task.processRequest()) {
|
||||||
task.execute();
|
new Thread(task).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (SocketTimeoutException e) {
|
} catch (SocketTimeoutException e) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Error connecting to client", e);
|
Log.e(TAG, "Error connecting to client", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Log.i(TAG, "Proxy interrupted. Shutting down.");
|
||||||
Log.d(TAG, "Proxy interrupted. Shutting down.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StreamToMediaPlayerTask extends AsyncTask<String, Void, Integer> {
|
private class StreamToMediaPlayerTask implements Runnable {
|
||||||
File thisFile;
|
|
||||||
|
String localPath;
|
||||||
Socket client;
|
Socket client;
|
||||||
int cbSkip;
|
int cbSkip;
|
||||||
|
|
||||||
|
@ -134,26 +122,32 @@ public class StreamProxy implements Runnable {
|
||||||
StringTokenizer st = new StringTokenizer(firstLine);
|
StringTokenizer st = new StringTokenizer(firstLine);
|
||||||
String method = st.nextToken();
|
String method = st.nextToken();
|
||||||
String uri = st.nextToken();
|
String uri = st.nextToken();
|
||||||
Log.d(TAG, uri);
|
|
||||||
String realUri = uri.substring(1);
|
String realUri = uri.substring(1);
|
||||||
Log.d(TAG, realUri);
|
Log.i(TAG, realUri);
|
||||||
request = new BasicHttpRequest(method, realUri);
|
request = new BasicHttpRequest(method, realUri);
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean processRequest() {
|
public boolean processRequest() {
|
||||||
HttpRequest request = readRequest();
|
HttpRequest request = readRequest();
|
||||||
|
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Processing request");
|
// Read HTTP headers
|
||||||
|
Log.i(TAG, "Processing request");
|
||||||
|
|
||||||
thisFile = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
|
try {
|
||||||
|
localPath = URLDecoder.decode(request.getRequestLine().getUri(), Constants.UTF_8);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
Log.e(TAG, "Unsupported encoding", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!thisFile.exists()) {
|
Log.i(TAG, "Processing request for file " + localPath);
|
||||||
Log.e(TAG, "File " + thisFile.getPath() + " does not exist");
|
File file = new File(localPath);
|
||||||
|
if (!file.exists()) {
|
||||||
|
Log.e(TAG, "File " + localPath + " does not exist");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,80 +155,75 @@ public class StreamProxy implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Integer doInBackground(String... params) {
|
public void run() {
|
||||||
long fileSize = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile().length() : downloadFile.getSong().getSize();
|
Log.i(TAG, "Streaming song in background");
|
||||||
|
DownloadFile downloadFile = downloadService.getCurrentPlaying();
|
||||||
|
MusicDirectory.Entry song = downloadFile.getSong();
|
||||||
|
long fileSize = downloadFile.getBitRate() * ((song.getDuration() != null) ? song.getDuration() : 0) * 1000 / 8;
|
||||||
|
Log.i(TAG, "Streaming fileSize: " + fileSize);
|
||||||
|
|
||||||
// Create HTTP header
|
// Create HTTP header
|
||||||
String headers = "HTTP/1.1 200 OK\r\n";
|
String headers = "HTTP/1.0 200 OK\r\n";
|
||||||
|
|
||||||
if (fileSize > 0) {
|
|
||||||
headers += "Content-Length: " + fileSize + "\r\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
headers += "Content-Type: " + "application/octet-stream" + "\r\n";
|
headers += "Content-Type: " + "application/octet-stream" + "\r\n";
|
||||||
|
|
||||||
headers += "Connection: close\r\n";
|
headers += "Connection: close\r\n";
|
||||||
headers += "\r\n";
|
headers += "\r\n";
|
||||||
|
|
||||||
long cbToSend = fileSize - cbSkip;
|
long cbToSend = fileSize - cbSkip;
|
||||||
long totalBytesSent = 0;
|
|
||||||
OutputStream output = null;
|
OutputStream output = null;
|
||||||
byte[] buff = new byte[64 * 1024];
|
byte[] buff = new byte[64 * 1024];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
output = new BufferedOutputStream(client.getOutputStream(), 32 * 1024);
|
output = new BufferedOutputStream(client.getOutputStream(), 32*1024);
|
||||||
output.write(headers.getBytes());
|
output.write(headers.getBytes());
|
||||||
|
|
||||||
|
if(!downloadFile.isWorkDone()) {
|
||||||
// Loop as long as there's stuff to send
|
// Loop as long as there's stuff to send
|
||||||
while (isRunning && cbToSend > 0 && !client.isClosed()) {
|
while (isRunning && !client.isClosed()) {
|
||||||
// See if there's more to send
|
|
||||||
int cbSentThisBatch = 0;
|
|
||||||
|
|
||||||
FileInputStream input = new FileInputStream(thisFile);
|
// See if there's more to send
|
||||||
|
File file = new File(localPath);
|
||||||
|
int cbSentThisBatch = 0;
|
||||||
|
if (file.exists()) {
|
||||||
|
FileInputStream input = new FileInputStream(file);
|
||||||
input.skip(cbSkip);
|
input.skip(cbSkip);
|
||||||
int cbToSendThisBatch = input.available();
|
int cbToSendThisBatch = input.available();
|
||||||
|
|
||||||
while (cbToSendThisBatch > 0) {
|
while (cbToSendThisBatch > 0) {
|
||||||
int cbToRead = Math.min(cbToSendThisBatch, buff.length);
|
int cbToRead = Math.min(cbToSendThisBatch, buff.length);
|
||||||
int cbRead = input.read(buff, 0, cbToRead);
|
int cbRead = input.read(buff, 0, cbToRead);
|
||||||
|
|
||||||
if (cbRead == -1) {
|
if (cbRead == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
cbToSendThisBatch -= cbRead;
|
cbToSendThisBatch -= cbRead;
|
||||||
cbToSend -= cbRead;
|
cbToSend -= cbRead;
|
||||||
|
|
||||||
output.write(buff, 0, cbRead);
|
output.write(buff, 0, cbRead);
|
||||||
output.flush();
|
output.flush();
|
||||||
|
|
||||||
cbSkip += cbRead;
|
cbSkip += cbRead;
|
||||||
cbSentThisBatch += cbRead;
|
cbSentThisBatch += cbRead;
|
||||||
totalBytesSent += cbRead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input.close();
|
input.close();
|
||||||
|
}
|
||||||
|
|
||||||
if (!downloadFile.isDownloading()) {
|
// Done regardless of whether or not it thinks it is
|
||||||
if (downloadFile.isCompleteFileAvailable()) {
|
if(downloadFile.isWorkDone() && cbSkip >= file.length()) {
|
||||||
if (downloadFile.getCompleteFile().length() == totalBytesSent) {
|
|
||||||
Log.d(TAG, "Track is no longer being downloaded, sent " + totalBytesSent + " / " + fileSize);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we did nothing this batch, block for a second
|
// If we did nothing this batch, block for a second
|
||||||
if (cbSentThisBatch == 0) {
|
if (cbSentThisBatch == 0) {
|
||||||
Log.d(TAG, "Blocking until more data appears");
|
Log.d(TAG, "Blocking until more data appears (" + cbToSend + ")");
|
||||||
Thread.sleep(500);
|
Thread.sleep(1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SocketException socketException) {
|
} else {
|
||||||
|
Log.w(TAG, "Requesting data for completely downloaded file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketException socketException) {
|
||||||
Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly");
|
Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly");
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
catch (Exception e) {
|
||||||
Log.e(TAG, "Exception thrown from streaming task:");
|
Log.e(TAG, "Exception thrown from streaming task:");
|
||||||
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
|
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
|
@ -243,13 +232,11 @@ public class StreamProxy implements Runnable {
|
||||||
output.close();
|
output.close();
|
||||||
}
|
}
|
||||||
client.close();
|
client.close();
|
||||||
} catch (IOException e) {
|
}
|
||||||
|
catch (IOException e) {
|
||||||
Log.e(TAG, "IOException while cleaning up streaming task:");
|
Log.e(TAG, "IOException while cleaning up streaming task:");
|
||||||
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
|
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -36,6 +36,8 @@ import android.graphics.drawable.Drawable;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
@ -401,6 +403,14 @@ public class Util extends DownloadActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void renameFile(File from, File to) throws IOException {
|
||||||
|
if(from.renameTo(to)) {
|
||||||
|
Log.i(TAG, "Renamed " + from + " to " + to);
|
||||||
|
} else {
|
||||||
|
atomicCopy(from, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void close(Closeable closeable) {
|
public static void close(Closeable closeable) {
|
||||||
try {
|
try {
|
||||||
if (closeable != null) {
|
if (closeable != null) {
|
||||||
|
@ -422,6 +432,24 @@ public class Util extends DownloadActivity {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean recursiveDelete(File dir) {
|
||||||
|
if (dir != null && dir.exists()) {
|
||||||
|
for(File file: dir.listFiles()) {
|
||||||
|
if(file.isDirectory()) {
|
||||||
|
if(!recursiveDelete(file)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if(file.exists()) {
|
||||||
|
if(!file.delete()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dir.delete();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static void toast(Context context, int messageId) {
|
public static void toast(Context context, int messageId) {
|
||||||
toast(context, messageId, true);
|
toast(context, messageId, true);
|
||||||
}
|
}
|
||||||
|
@ -796,6 +824,11 @@ public class Util extends DownloadActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static WifiManager.WifiLock createWifiLock(Context context, String tag) {
|
||||||
|
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||||
|
return wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, tag);
|
||||||
|
}
|
||||||
|
|
||||||
public static Bitmap scaleBitmap(Bitmap bitmap, int size) {
|
public static Bitmap scaleBitmap(Bitmap bitmap, int size) {
|
||||||
// Try to keep correct aspect ratio of the original image, do not force a square
|
// Try to keep correct aspect ratio of the original image, do not force a square
|
||||||
double aspectRatio = (double)bitmap.getHeight() / (double)bitmap.getWidth();
|
double aspectRatio = (double)bitmap.getHeight() / (double)bitmap.getWidth();
|
||||||
|
@ -1088,6 +1121,18 @@ public class Util extends DownloadActivity {
|
||||||
views.setOnClickPendingIntent(R.id.control_stop, pendingIntent);
|
views.setOnClickPendingIntent(R.id.control_stop, pendingIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getMaxVideoBitrate(Context context) {
|
||||||
|
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
|
||||||
|
if (networkInfo == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
|
||||||
|
SharedPreferences prefs = getPreferences(context);
|
||||||
|
return Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_WIFI : Constants.PREFERENCES_KEY_MAX_VIDEO_BITRATE_MOBILE, "0"));
|
||||||
|
}
|
||||||
|
|
||||||
public static int getNetworkTimeout(Context context) {
|
public static int getNetworkTimeout(Context context) {
|
||||||
SharedPreferences prefs = getPreferences(context);
|
SharedPreferences prefs = getPreferences(context);
|
||||||
return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, "15000"));
|
return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, "15000"));
|
||||||
|
@ -1137,4 +1182,14 @@ public class Util extends DownloadActivity {
|
||||||
SharedPreferences prefs = getPreferences(context);
|
SharedPreferences prefs = getPreferences(context);
|
||||||
return prefs.getBoolean(Constants.PREFERENCES_KEY_SHOW_NOW_PLAYING, true);
|
return prefs.getBoolean(Constants.PREFERENCES_KEY_SHOW_NOW_PLAYING, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean getGaplessPlaybackPreference(Context context) {
|
||||||
|
SharedPreferences prefs = getPreferences(context);
|
||||||
|
return prefs.getBoolean(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getShouldTransitionOnPlaybackPreference(Context context) {
|
||||||
|
SharedPreferences prefs = getPreferences(context);
|
||||||
|
return prefs.getBoolean(Constants.PREFERENCES_KEY_DOWNLOAD_TRANSITION, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,26 +16,27 @@
|
||||||
|
|
||||||
Copyright 2009 (C) Sindre Mehus
|
Copyright 2009 (C) Sindre Mehus
|
||||||
*/
|
*/
|
||||||
package com.thejoshwa.ultrasonic.androidapp.util;
|
package com.thejoshwa.ultrasonic.androidapp.view;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.R;
|
import com.thejoshwa.ultrasonic.androidapp.R;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.util.ImageLoader;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to display albums in a {@code ListView}.
|
* Used to display albums in a {@code ListView}.
|
||||||
*
|
*
|
||||||
* @author Sindre Mehus
|
* @author Sindre Mehus
|
||||||
*/
|
*/
|
||||||
public class AlbumView extends LinearLayout {
|
public class AlbumView extends UpdateView {
|
||||||
|
|
||||||
private static final String TAG = AlbumView.class.getSimpleName();
|
private static final String TAG = AlbumView.class.getSimpleName();
|
||||||
private TextView titleView;
|
private TextView titleView;
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
Copyright 2010 (C) Sindre Mehus
|
Copyright 2010 (C) Sindre Mehus
|
||||||
*/
|
*/
|
||||||
package com.thejoshwa.ultrasonic.androidapp.util;
|
package com.thejoshwa.ultrasonic.androidapp.view;
|
||||||
|
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.Artist;
|
import com.thejoshwa.ultrasonic.androidapp.domain.Artist;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.R;
|
import com.thejoshwa.ultrasonic.androidapp.R;
|
|
@ -0,0 +1,86 @@
|
||||||
|
package com.thejoshwa.ultrasonic.androidapp.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
public class AutoRepeatButton extends ImageView {
|
||||||
|
|
||||||
|
private long initialRepeatDelay = 1000;
|
||||||
|
private long repeatIntervalInMilliseconds = 300;
|
||||||
|
private boolean doClick = true;
|
||||||
|
private Runnable repeatEvent = null;
|
||||||
|
|
||||||
|
private Runnable repeatClickWhileButtonHeldRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
doClick = false;
|
||||||
|
//Perform the present repetition of the click action provided by the user
|
||||||
|
// in setOnClickListener().
|
||||||
|
if(repeatEvent != null)
|
||||||
|
repeatEvent.run();
|
||||||
|
|
||||||
|
//Schedule the next repetitions of the click action, using a faster repeat
|
||||||
|
// interval than the initial repeat delay interval.
|
||||||
|
postDelayed(repeatClickWhileButtonHeldRunnable, repeatIntervalInMilliseconds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void commonConstructorCode() {
|
||||||
|
this.setOnTouchListener(new OnTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
int action = event.getAction();
|
||||||
|
if(action == MotionEvent.ACTION_DOWN)
|
||||||
|
{
|
||||||
|
doClick = true;
|
||||||
|
//Just to be sure that we removed all callbacks,
|
||||||
|
// which should have occurred in the ACTION_UP
|
||||||
|
removeCallbacks(repeatClickWhileButtonHeldRunnable);
|
||||||
|
|
||||||
|
//Schedule the start of repetitions after a one half second delay.
|
||||||
|
postDelayed(repeatClickWhileButtonHeldRunnable, initialRepeatDelay);
|
||||||
|
|
||||||
|
setPressed(true);
|
||||||
|
}
|
||||||
|
else if(action == MotionEvent.ACTION_UP) {
|
||||||
|
//Cancel any repetition in progress.
|
||||||
|
removeCallbacks(repeatClickWhileButtonHeldRunnable);
|
||||||
|
|
||||||
|
if(doClick || repeatEvent == null) {
|
||||||
|
performClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
setPressed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Returning true here prevents performClick() from getting called
|
||||||
|
// in the usual manner, which would be redundant, given that we are
|
||||||
|
// already calling it above.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnRepeatListener(Runnable runnable) {
|
||||||
|
repeatEvent = runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AutoRepeatButton(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
commonConstructorCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public AutoRepeatButton(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
commonConstructorCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AutoRepeatButton(Context context) {
|
||||||
|
super(context);
|
||||||
|
commonConstructorCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
Copyright 2010 (C) Sindre Mehus
|
Copyright 2010 (C) Sindre Mehus
|
||||||
*/
|
*/
|
||||||
package com.thejoshwa.ultrasonic.androidapp.util;
|
package com.thejoshwa.ultrasonic.androidapp.view;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.activity.SubsonicTabActivity;
|
import com.thejoshwa.ultrasonic.androidapp.activity.SubsonicTabActivity;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.util.ImageLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Sindre Mehus
|
* @author Sindre Mehus
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
Copyright 2010 (C) Sindre Mehus
|
Copyright 2010 (C) Sindre Mehus
|
||||||
*/
|
*/
|
||||||
package com.thejoshwa.ultrasonic.androidapp.util;
|
package com.thejoshwa.ultrasonic.androidapp.view;
|
||||||
|
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.Genre;
|
import com.thejoshwa.ultrasonic.androidapp.domain.Genre;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.R;
|
import com.thejoshwa.ultrasonic.androidapp.R;
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.thejoshwa.ultrasonic.androidapp.view;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.R;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.activity.SubsonicTabActivity;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.domain.Playlist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sindre Mehus
|
||||||
|
*/
|
||||||
|
public class PlaylistAdapter extends ArrayAdapter<Playlist> {
|
||||||
|
|
||||||
|
private final SubsonicTabActivity activity;
|
||||||
|
|
||||||
|
public PlaylistAdapter(SubsonicTabActivity activity, List<Playlist> Playlists) {
|
||||||
|
super(activity, R.layout.playlist_list_item, Playlists);
|
||||||
|
this.activity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
Playlist entry = getItem(position);
|
||||||
|
PlaylistView view;
|
||||||
|
if (convertView != null && convertView instanceof PlaylistView) {
|
||||||
|
view = (PlaylistView) convertView;
|
||||||
|
} else {
|
||||||
|
view = new PlaylistView(activity);
|
||||||
|
}
|
||||||
|
view.setPlaylist(entry);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PlaylistComparator implements Comparator<Playlist> {
|
||||||
|
@Override
|
||||||
|
public int compare(Playlist playlist1, Playlist playlist2) {
|
||||||
|
return playlist1.getName().compareToIgnoreCase(playlist2.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Playlist> sort(List<Playlist> playlists) {
|
||||||
|
Collections.sort(playlists, new PlaylistComparator());
|
||||||
|
return playlists;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
This file is part of Subsonic.
|
||||||
|
|
||||||
|
Subsonic is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Subsonic is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Copyright 2009 (C) Sindre Mehus
|
||||||
|
*/
|
||||||
|
package com.thejoshwa.ultrasonic.androidapp.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.R;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.domain.Playlist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to display albums in a {@code ListView}.
|
||||||
|
*
|
||||||
|
* @author Sindre Mehus
|
||||||
|
*/
|
||||||
|
public class PlaylistView extends UpdateView {
|
||||||
|
private static final String TAG = PlaylistView.class.getSimpleName();
|
||||||
|
|
||||||
|
private TextView titleView;
|
||||||
|
public PlaylistView(Context context) {
|
||||||
|
super(context);
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.playlist_list_item, this, true);
|
||||||
|
|
||||||
|
titleView = (TextView) findViewById(R.id.playlist_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlaylist(Playlist playlist) {
|
||||||
|
titleView.setText(playlist.getName());
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,18 +16,16 @@
|
||||||
|
|
||||||
Copyright 2009 (C) Sindre Mehus
|
Copyright 2009 (C) Sindre Mehus
|
||||||
*/
|
*/
|
||||||
package com.thejoshwa.ultrasonic.androidapp.util;
|
package com.thejoshwa.ultrasonic.androidapp.view;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Checkable;
|
import android.widget.Checkable;
|
||||||
import android.widget.CheckedTextView;
|
import android.widget.CheckedTextView;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.R;
|
import com.thejoshwa.ultrasonic.androidapp.R;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||||
|
@ -36,31 +34,29 @@ import com.thejoshwa.ultrasonic.androidapp.service.DownloadServiceImpl;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
||||||
|
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.WeakHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to display songs in a {@code ListView}.
|
* Used to display songs in a {@code ListView}.
|
||||||
*
|
*
|
||||||
* @author Sindre Mehus
|
* @author Sindre Mehus
|
||||||
*/
|
*/
|
||||||
public class SongView extends LinearLayout implements Checkable {
|
public class SongView extends UpdateView implements Checkable {
|
||||||
|
|
||||||
private static final String TAG = SongView.class.getSimpleName();
|
private static final String TAG = SongView.class.getSimpleName();
|
||||||
private static final WeakHashMap<SongView, ?> INSTANCES = new WeakHashMap<SongView, Object>();
|
|
||||||
private static Handler handler;
|
|
||||||
|
|
||||||
private CheckedTextView checkedTextView;
|
private CheckedTextView checkedTextView;
|
||||||
private ImageView starImageView;
|
private ImageView starImageView;
|
||||||
private TextView trackTextView;
|
private TextView trackTextView;
|
||||||
private TextView discTextView;
|
|
||||||
private TextView titleTextView;
|
private TextView titleTextView;
|
||||||
private TextView artistTextView;
|
private TextView artistTextView;
|
||||||
private TextView durationTextView;
|
private TextView durationTextView;
|
||||||
private TextView statusTextView;
|
private TextView statusTextView;
|
||||||
private MusicDirectory.Entry song;
|
private MusicDirectory.Entry song;
|
||||||
|
|
||||||
|
private DownloadService downloadService;
|
||||||
|
|
||||||
public SongView(Context context) {
|
public SongView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
LayoutInflater.from(context).inflate(R.layout.song_list_item, this, true);
|
LayoutInflater.from(context).inflate(R.layout.song_list_item, this, true);
|
||||||
|
@ -72,15 +68,6 @@ public class SongView extends LinearLayout implements Checkable {
|
||||||
artistTextView = (TextView) findViewById(R.id.song_artist);
|
artistTextView = (TextView) findViewById(R.id.song_artist);
|
||||||
durationTextView = (TextView) findViewById(R.id.song_duration);
|
durationTextView = (TextView) findViewById(R.id.song_duration);
|
||||||
statusTextView = (TextView) findViewById(R.id.song_status);
|
statusTextView = (TextView) findViewById(R.id.song_status);
|
||||||
|
|
||||||
INSTANCES.put(this, null);
|
|
||||||
int instanceCount = INSTANCES.size();
|
|
||||||
|
|
||||||
if (instanceCount > 50) {
|
|
||||||
Log.w(TAG, instanceCount + " live SongView instances");
|
|
||||||
}
|
|
||||||
|
|
||||||
startUpdater();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSong(final MusicDirectory.Entry song, boolean checkable) {
|
public void setSong(final MusicDirectory.Entry song, boolean checkable) {
|
||||||
|
@ -157,23 +144,38 @@ public class SongView extends LinearLayout implements Checkable {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateBackground();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update() {
|
@Override
|
||||||
DownloadService downloadService = DownloadServiceImpl.getInstance();
|
protected void updateBackground() {
|
||||||
|
if (downloadService == null) {
|
||||||
|
|
||||||
|
downloadService = DownloadServiceImpl.getInstance();
|
||||||
|
|
||||||
|
if(downloadService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadService.forSong(song);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void update() {
|
||||||
if (downloadService == null) {
|
if (downloadService == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadFile downloadFile = downloadService.forSong(song);
|
DownloadFile downloadFile = downloadService.forSong(song);
|
||||||
File completeFile = downloadFile.getCompleteFile();
|
downloadFile.getCompleteFile();
|
||||||
File partialFile = downloadFile.getPartialFile();
|
File partialFile = downloadFile.getPartialFile();
|
||||||
|
|
||||||
Drawable leftImage = null;
|
Drawable leftImage = null;
|
||||||
Drawable rightImage = null;
|
Drawable rightImage = null;
|
||||||
|
|
||||||
if (completeFile.exists()) {
|
if (downloadFile.isWorkDone()) {
|
||||||
leftImage = downloadFile.isSaved() ? Util.getDrawableFromAttribute(getContext(), R.attr.unpin) : Util.getDrawableFromAttribute(getContext(), R.attr.downloaded);
|
leftImage = downloadFile.isSaved() ? Util.getDrawableFromAttribute(getContext(), R.attr.unpin) : Util.getDrawableFromAttribute(getContext(), R.attr.downloaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,34 +202,6 @@ public class SongView extends LinearLayout implements Checkable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static synchronized void startUpdater() {
|
|
||||||
if (handler != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handler = new Handler();
|
|
||||||
Runnable runnable = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
updateAll();
|
|
||||||
handler.postDelayed(this, 1000L);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
handler.postDelayed(runnable, 1000L);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void updateAll() {
|
|
||||||
try {
|
|
||||||
for (SongView view : INSTANCES.keySet()) {
|
|
||||||
if (view.isShown()) {
|
|
||||||
view.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable x) {
|
|
||||||
Log.w(TAG, "Error when updating song views.", x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setChecked(boolean b) {
|
public void setChecked(boolean b) {
|
||||||
checkedTextView.setChecked(b);
|
checkedTextView.setChecked(b);
|
|
@ -0,0 +1,115 @@
|
||||||
|
package com.thejoshwa.ultrasonic.androidapp.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AbsListView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
public class UpdateView extends LinearLayout {
|
||||||
|
private static final String TAG = UpdateView.class.getSimpleName();
|
||||||
|
private static final WeakHashMap<UpdateView, ?> INSTANCES = new WeakHashMap<UpdateView, Object>();
|
||||||
|
|
||||||
|
private static Handler backgroundHandler;
|
||||||
|
private static Handler uiHandler;
|
||||||
|
private static Runnable updateRunnable;
|
||||||
|
|
||||||
|
public UpdateView(Context context) {
|
||||||
|
super(context);
|
||||||
|
|
||||||
|
setLayoutParams(new AbsListView.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.FILL_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
|
INSTANCES.put(this, null);
|
||||||
|
int instanceCount = INSTANCES.size();
|
||||||
|
if (instanceCount > 50) {
|
||||||
|
Log.w(TAG, instanceCount + " live UpdateView instances");
|
||||||
|
}
|
||||||
|
|
||||||
|
startUpdater();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPressed(boolean pressed) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static synchronized void startUpdater() {
|
||||||
|
if(uiHandler != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uiHandler = new Handler();
|
||||||
|
updateRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
updateAll();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
Looper.prepare();
|
||||||
|
backgroundHandler = new Handler(Looper.myLooper());
|
||||||
|
uiHandler.post(updateRunnable);
|
||||||
|
Looper.loop();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void updateAll() {
|
||||||
|
try {
|
||||||
|
List<UpdateView> views = new ArrayList<UpdateView>();;
|
||||||
|
for (UpdateView view : INSTANCES.keySet()) {
|
||||||
|
if (view.isShown()) {
|
||||||
|
views.add(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateAllLive(views);
|
||||||
|
} catch (Throwable x) {
|
||||||
|
Log.w(TAG, "Error when updating song views.", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static void updateAllLive(final List<UpdateView> views) {
|
||||||
|
final Runnable runnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
for(UpdateView view: views) {
|
||||||
|
view.update();
|
||||||
|
}
|
||||||
|
} catch (Throwable x) {
|
||||||
|
Log.w(TAG, "Error when updating song views.", x);
|
||||||
|
}
|
||||||
|
uiHandler.postDelayed(updateRunnable, 1000L);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
backgroundHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
for(UpdateView view: views) {
|
||||||
|
view.updateBackground();
|
||||||
|
}
|
||||||
|
uiHandler.post(runnable);
|
||||||
|
} catch (Throwable x) {
|
||||||
|
Log.w(TAG, "Error when updating song views.", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateBackground() {
|
||||||
|
|
||||||
|
}
|
||||||
|
protected void update() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue