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 static final class id {
|
||||
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 both = 0x7f060003;
|
||||
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 gridview = 0x7f060009;
|
||||
public static final int pullUpFromBottom = 0x7f060006;
|
||||
public static final int scrollview = 0x7f06000b;
|
||||
public static final int pullFromEnd = 0x7f060002;
|
||||
public static final int pull_to_refresh_image = 0x7f06007e;
|
||||
public static final int pull_to_refresh_sub_text = 0x7f060081;
|
||||
public static final int fl_inner = 0x7f06007d;
|
||||
public static final int pull_to_refresh_image = 0x7f060080;
|
||||
public static final int pull_to_refresh_sub_text = 0x7f060083;
|
||||
public static final int fl_inner = 0x7f06007f;
|
||||
public static final int flip = 0x7f060008;
|
||||
public static final int disabled = 0x7f060000;
|
||||
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 class layout {
|
||||
public static final int pull_to_refresh_header_vertical = 0x7f030018;
|
||||
public static final int pull_to_refresh_header_horizontal = 0x7f030017;
|
||||
public static final int pull_to_refresh_header_vertical = 0x7f030019;
|
||||
public static final int pull_to_refresh_header_horizontal = 0x7f030018;
|
||||
}
|
||||
public static final class styleable {
|
||||
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 class drawable {
|
||||
public static final int indicator_bg_top = 0x7f020045;
|
||||
public static final int indicator_bg_bottom = 0x7f020044;
|
||||
public static final int indicator_bg_top = 0x7f020046;
|
||||
public static final int indicator_bg_bottom = 0x7f020045;
|
||||
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 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: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: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" />
|
||||
<include layout="@layout/media_buttons" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
@ -64,99 +64,8 @@
|
|||
<include layout="@layout/download_playlist" />
|
||||
</com.thejoshwa.ultrasonic.androidapp.util.MyViewFlipper>
|
||||
|
||||
<LinearLayout
|
||||
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/media_buttons" />
|
||||
|
||||
<include layout="@layout/download_slider" />
|
||||
|
||||
<include layout="@layout/download_button_bar_flipper" />
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp" >
|
||||
android:padding="6dp" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/select_album_select"
|
||||
|
|
|
@ -1,47 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
a:orientation="vertical"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="fill_parent"
|
||||
a:background="@drawable/album_art_background"
|
||||
a:padding="16dip">
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:padding="16dip">
|
||||
|
||||
<CheckBox
|
||||
a:id="@+id/equalizer_enabled"
|
||||
a:layout_width="wrap_content"
|
||||
a:layout_height="wrap_content"
|
||||
a:text="@string/equalizer.enabled"
|
||||
a:textColor="#c0c0c0"
|
||||
a:textAppearance="?android:attr/textAppearanceMedium"/>
|
||||
android:id="@+id/equalizer_enabled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/equalizer.enabled"
|
||||
android:textColor="#c0c0c0"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"/>
|
||||
|
||||
<ScrollView
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="wrap_content">
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
a:orientation="vertical"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="wrap_content">
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
a:id="@+id/equalizer_layout"
|
||||
a:orientation="vertical"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="wrap_content"/>
|
||||
android:id="@+id/equalizer_layout"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<Button
|
||||
a:id="@+id/equalizer_preset"
|
||||
a:text="@string/equalizer.preset"
|
||||
a:layout_width="wrap_content"
|
||||
a:layout_height="wrap_content"
|
||||
a:layout_gravity="center"
|
||||
a:layout_marginTop="20dip"
|
||||
a:paddingLeft="40dip"
|
||||
a:paddingRight="40dip"/>
|
||||
android:id="@+id/equalizer_preset"
|
||||
android:text="@string/equalizer.preset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="20dip"
|
||||
android:paddingLeft="40dip"
|
||||
android:paddingRight="40dip"/>
|
||||
|
||||
</LinearLayout>
|
||||
</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_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="@drawable/border"
|
||||
android:orientation="horizontal"
|
||||
android:orientation="vertical"
|
||||
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
|
||||
android:id="@+id/now_playing_image"
|
||||
android:layout_width="64.0dip"
|
||||
android:layout_height="64.0dip"
|
||||
android:layout_weight="0.0"
|
||||
android:focusable="true"
|
||||
android:gravity="center" />
|
||||
|
||||
|
@ -55,5 +64,7 @@
|
|||
android:layout_weight="0.0"
|
||||
android:focusable="false"
|
||||
android:src="?attr/media_pause" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -1,10 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:a="http://schemas.android.com/apk/res/android"
|
||||
a:id="@android:id/text1"
|
||||
a:layout_width="fill_parent"
|
||||
a:layout_height="wrap_content"
|
||||
a:textAppearance="?android:attr/textAppearanceMedium"
|
||||
a:gravity="center_vertical"
|
||||
a:paddingLeft="6dip"
|
||||
a:paddingRight="6dip"
|
||||
a:minHeight="50dip"/>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playlist_name"
|
||||
android:layout_width="0dip"
|
||||
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.various_artists">Various Artists</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.browse">Media Library</string>
|
||||
<string name="button_bar.search">Search</string>
|
||||
|
@ -47,8 +53,15 @@
|
|||
<string name="menu.about">About</string>
|
||||
<string name="menu.search">Search</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.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.title">Welcome to UltraSonic</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_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="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.kbps">%d kbps</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.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.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_summary">Show currently playing track in all activities</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_albums">Default Albums</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="background_task.wait">Please wait…</string>
|
||||
<string name="background_task.loading">Loading.</string>
|
||||
|
|
|
@ -159,6 +159,16 @@
|
|||
a:key="useStreamProxy"
|
||||
a:summary="@string/settings.use_stream_proxy_summary"
|
||||
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 a:title="@string/settings.other_title" >
|
||||
<CheckBoxPreference
|
||||
|
|
|
@ -51,7 +51,6 @@ import android.view.animation.AnimationUtils;
|
|||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
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.util.Constants;
|
||||
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.view.AutoRepeatButton;
|
||||
import com.thejoshwa.ultrasonic.androidapp.view.SongView;
|
||||
import com.thejoshwa.ultrasonic.androidapp.view.VisualizerView;
|
||||
|
||||
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 {
|
||||
private static final String TAG = DownloadActivity.class.getSimpleName();
|
||||
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 ViewFlipper playlistFlipper;
|
||||
private ViewFlipper buttonBarFlipper;
|
||||
private TextView emptyTextView;
|
||||
private TextView songTitleTextView;
|
||||
private TextView albumTextView;
|
||||
|
@ -92,8 +92,8 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
private TextView durationTextView;
|
||||
private TextView statusTextView;
|
||||
private static SeekBar progressBar;
|
||||
private View previousButton;
|
||||
private View nextButton;
|
||||
private AutoRepeatButton previousButton;
|
||||
private AutoRepeatButton nextButton;
|
||||
private View pauseButton;
|
||||
private View stopButton;
|
||||
private View startButton;
|
||||
|
@ -112,8 +112,11 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
private int swipeDistance;
|
||||
private int swipeVelocity;
|
||||
private VisualizerView visualizerView;
|
||||
private boolean nowPlaying = true;
|
||||
private boolean visualizerAvailable;
|
||||
private boolean equalizerAvailable;
|
||||
private SilentBackgroundTask<Void> onProgressChangedTask;
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
|
@ -129,7 +132,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
gestureScanner = new GestureDetector(this);
|
||||
|
||||
playlistFlipper = (ViewFlipper) findViewById(R.id.download_playlist_flipper);
|
||||
buttonBarFlipper = (ViewFlipper) findViewById(R.id.download_button_bar_flipper);
|
||||
emptyTextView = (TextView) findViewById(R.id.download_empty);
|
||||
songTitleTextView = (TextView) findViewById(R.id.download_song_title);
|
||||
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);
|
||||
progressBar = (SeekBar) findViewById(R.id.download_progress_bar);
|
||||
playlistView = (ListView) findViewById(R.id.download_list);
|
||||
previousButton = findViewById(R.id.download_previous);
|
||||
nextButton = findViewById(R.id.download_next);
|
||||
previousButton = (AutoRepeatButton)findViewById(R.id.download_previous);
|
||||
nextButton = (AutoRepeatButton)findViewById(R.id.download_next);
|
||||
pauseButton = findViewById(R.id.download_pause);
|
||||
stopButton = findViewById(R.id.download_stop);
|
||||
startButton = findViewById(R.id.download_start);
|
||||
|
@ -152,21 +154,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
|
||||
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() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
|
@ -177,40 +164,98 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
previousButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
getDownloadService().previous();
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
|
||||
new SilentBackgroundTask<Void>(DownloadActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
getDownloadService().previous();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
previousButton.setOnRepeatListener(new Runnable() {
|
||||
public void run() {
|
||||
changeProgress(-INCREMENT_TIME);
|
||||
}
|
||||
});
|
||||
|
||||
nextButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
if (getDownloadService().getCurrentPlayingIndex() < getDownloadService().size() - 1) {
|
||||
getDownloadService().next();
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
}
|
||||
|
||||
new SilentBackgroundTask<Boolean>(DownloadActivity.this) {
|
||||
@Override
|
||||
protected Boolean doInBackground() throws Throwable {
|
||||
if (getDownloadService().getCurrentPlayingIndex() < getDownloadService().size() - 1) {
|
||||
getDownloadService().next();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Boolean result) {
|
||||
if(result) {
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
nextButton.setOnRepeatListener(new Runnable() {
|
||||
public void run() {
|
||||
changeProgress(INCREMENT_TIME);
|
||||
}
|
||||
});
|
||||
|
||||
pauseButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
getDownloadService().pause();
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
new SilentBackgroundTask<Void>(DownloadActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
getDownloadService().pause();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
stopButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
getDownloadService().reset();
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
new SilentBackgroundTask<Void>(DownloadActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
getDownloadService().reset();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -218,9 +263,20 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
@Override
|
||||
public void onClick(View view) {
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
start();
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
|
||||
new SilentBackgroundTask<Void>(DownloadActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
start();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -265,8 +321,19 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
getDownloadService().seekTo(getProgressBar().getProgress());
|
||||
onSliderProgressChanged();
|
||||
new SilentBackgroundTask<Void>(DownloadActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
getDownloadService().seekTo(getProgressBar().getProgress());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
onSliderProgressChanged();
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -280,11 +347,22 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
|
||||
playlistView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@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();
|
||||
getDownloadService().play(position);
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
|
||||
new SilentBackgroundTask<Void>(DownloadActivity.this) {
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
getDownloadService().play(position);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -381,7 +459,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
DownloadService downloadService = getDownloadService();
|
||||
if (downloadService == null || downloadService.getCurrentPlaying() == null) {
|
||||
playlistFlipper.setDisplayedChild(1);
|
||||
buttonBarFlipper.setDisplayedChild(1);
|
||||
}
|
||||
|
||||
onDownloadListChanged();
|
||||
|
@ -461,11 +538,11 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
return super.onCreateDialog(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialog(int id, Dialog dialog) {
|
||||
if (id == DIALOG_SAVE_PLAYLIST) {
|
||||
String playlistName = getDownloadService().getSuggestedPlaylistName();
|
||||
String playlistName = (getDownloadService() != null) ? getDownloadService().getSuggestedPlaylistName() : null;
|
||||
if (playlistName != null) {
|
||||
playlistNameView.setText(playlistName);
|
||||
} else {
|
||||
|
@ -623,7 +700,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
@Override
|
||||
protected Void doInBackground() throws Throwable {
|
||||
List<MusicDirectory.Entry> entries = new LinkedList<MusicDirectory.Entry>();
|
||||
for (DownloadFile downloadFile : getDownloadService().getDownloads()) {
|
||||
for (DownloadFile downloadFile : getDownloadService().getSongs()) {
|
||||
entries.add(downloadFile.getSong());
|
||||
}
|
||||
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.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_down_out));
|
||||
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 {
|
||||
playlistFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_in));
|
||||
playlistFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_out));
|
||||
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() {
|
||||
DownloadService service = getDownloadService();
|
||||
PlayerState state = service.getPlayerState();
|
||||
if (state == PAUSED || state == COMPLETED) {
|
||||
if (state == PAUSED || state == COMPLETED || state == STOPPED) {
|
||||
service.start();
|
||||
} else if (state == STOPPED || state == IDLE) {
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
|
@ -682,30 +751,41 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
}
|
||||
}
|
||||
|
||||
private void onDownloadListChanged() {
|
||||
private void onDownloadListChanged() {
|
||||
onDownloadListChanged(false);
|
||||
}
|
||||
|
||||
private void onDownloadListChanged(boolean refresh) {
|
||||
DownloadService downloadService = getDownloadService();
|
||||
if (downloadService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<DownloadFile> list = downloadService.getDownloads();
|
||||
|
||||
playlistView.setAdapter(new SongListAdapter(list));
|
||||
List<DownloadFile> list;
|
||||
if(nowPlaying) {
|
||||
list = downloadService.getSongs();
|
||||
}
|
||||
else {
|
||||
list = downloadService.getBackgroundDownloads();
|
||||
}
|
||||
|
||||
emptyTextView.setText(R.string.download_empty);
|
||||
playlistView.setAdapter(new SongListAdapter(list));
|
||||
emptyTextView.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
currentRevision = downloadService.getDownloadListUpdateRevision();
|
||||
|
||||
switch (downloadService.getRepeatMode()) {
|
||||
case OFF:
|
||||
repeatButton.setImageDrawable(Util.getDrawableFromAttribute(this, R.attr.media_repeat_off));
|
||||
break;
|
||||
case ALL:
|
||||
repeatButton.setImageDrawable(Util.getDrawableFromAttribute(this, R.attr.media_repeat_all));
|
||||
break;
|
||||
case SINGLE:
|
||||
repeatButton.setImageDrawable(Util.getDrawableFromAttribute(this, R.attr.media_repeat_single));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case OFF:
|
||||
repeatButton.setImageDrawable(Util.getDrawableFromAttribute(this, R.attr.media_repeat_off));
|
||||
break;
|
||||
case ALL:
|
||||
repeatButton.setImageDrawable(Util.getDrawableFromAttribute(this, R.attr.media_repeat_all));
|
||||
break;
|
||||
case SINGLE:
|
||||
repeatButton.setImageDrawable(Util.getDrawableFromAttribute(this, R.attr.media_repeat_single));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -732,70 +812,128 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
|||
}
|
||||
}
|
||||
|
||||
private void onSliderProgressChanged() {
|
||||
if (getDownloadService() == null) {
|
||||
return;
|
||||
}
|
||||
private void onSliderProgressChanged() {
|
||||
if (getDownloadService() == null || onProgressChangedTask != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPlaying != null) {
|
||||
int millisPlayed = Math.max(0, getDownloadService().getPlayerPosition());
|
||||
Integer duration = getDownloadService().getPlayerDuration();
|
||||
int millisTotal = duration == null ? 0 : duration;
|
||||
onProgressChangedTask = new SilentBackgroundTask<Void>(this) {
|
||||
DownloadService downloadService;
|
||||
boolean isJukeboxEnabled;
|
||||
int millisPlayed;
|
||||
Integer duration;
|
||||
PlayerState playerState;
|
||||
|
||||
positionTextView.setText(Util.formatDuration(millisPlayed / 1000));
|
||||
durationTextView.setText(Util.formatDuration(millisTotal / 1000));
|
||||
progressBar.setMax(millisTotal == 0 ? 100 : millisTotal); // Work-around for apparent bug.
|
||||
progressBar.setProgress(millisPlayed);
|
||||
progressBar.setEnabled(currentPlaying.isCompleteFileAvailable() || getDownloadService().isJukeboxEnabled());
|
||||
} else {
|
||||
positionTextView.setText(R.string.util_zero_time);
|
||||
durationTextView.setText(R.string.util_no_time);
|
||||
progressBar.setProgress(0);
|
||||
progressBar.setMax(0);
|
||||
progressBar.setEnabled(false);
|
||||
}
|
||||
@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;
|
||||
}
|
||||
|
||||
PlayerState playerState = getDownloadService().getPlayerState();
|
||||
@Override
|
||||
protected void done(Void result) {
|
||||
if (currentPlaying != null) {
|
||||
int millisTotal = duration == null ? 0 : duration;
|
||||
|
||||
switch (playerState) {
|
||||
case DOWNLOADING:
|
||||
long bytes = currentPlaying.getPartialFile().length();
|
||||
statusTextView.setText(getResources().getString(R.string.download_playerstate_downloading, Util.formatLocalizedBytes(bytes, this)));
|
||||
break;
|
||||
case PREPARING:
|
||||
statusTextView.setText(R.string.download_playerstate_buffering);
|
||||
break;
|
||||
case STARTED:
|
||||
if (getDownloadService().isShufflePlayEnabled()) {
|
||||
statusTextView.setText(R.string.download_playerstate_playing_shuffle);
|
||||
} else {
|
||||
statusTextView.setText(null);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
statusTextView.setText(null);
|
||||
break;
|
||||
}
|
||||
positionTextView.setText(Util.formatDuration(millisPlayed / 1000));
|
||||
durationTextView.setText(Util.formatDuration(millisTotal / 1000));
|
||||
progressBar.setMax(millisTotal == 0 ? 100 : millisTotal); // Work-around for apparent bug.
|
||||
progressBar.setProgress(millisPlayed);
|
||||
progressBar.setEnabled(currentPlaying.isWorkDone() || isJukeboxEnabled);
|
||||
} else {
|
||||
positionTextView.setText(R.string.util_zero_time);
|
||||
durationTextView.setText(R.string.util_no_time);
|
||||
progressBar.setProgress(0);
|
||||
progressBar.setMax(0);
|
||||
progressBar.setEnabled(false);
|
||||
}
|
||||
|
||||
switch (playerState) {
|
||||
case STARTED:
|
||||
pauseButton.setVisibility(View.VISIBLE);
|
||||
stopButton.setVisibility(View.GONE);
|
||||
startButton.setVisibility(View.GONE);
|
||||
break;
|
||||
case DOWNLOADING:
|
||||
case PREPARING:
|
||||
pauseButton.setVisibility(View.GONE);
|
||||
stopButton.setVisibility(View.VISIBLE);
|
||||
startButton.setVisibility(View.GONE);
|
||||
break;
|
||||
default:
|
||||
pauseButton.setVisibility(View.GONE);
|
||||
stopButton.setVisibility(View.GONE);
|
||||
startButton.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (playerState) {
|
||||
case DOWNLOADING:
|
||||
long bytes = currentPlaying.getPartialFile().length();
|
||||
statusTextView.setText(getResources().getString(
|
||||
R.string.download_playerstate_downloading,
|
||||
Util.formatLocalizedBytes(bytes,
|
||||
DownloadActivity.this)));
|
||||
break;
|
||||
case PREPARING:
|
||||
statusTextView
|
||||
.setText(R.string.download_playerstate_buffering);
|
||||
break;
|
||||
case STARTED:
|
||||
if (getDownloadService().isShufflePlayEnabled()) {
|
||||
statusTextView
|
||||
.setText(R.string.download_playerstate_playing_shuffle);
|
||||
} else {
|
||||
statusTextView.setText(null);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
statusTextView.setText(null);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (playerState) {
|
||||
case STARTED:
|
||||
pauseButton.setVisibility(View.VISIBLE);
|
||||
stopButton.setVisibility(View.GONE);
|
||||
startButton.setVisibility(View.GONE);
|
||||
break;
|
||||
case DOWNLOADING:
|
||||
case PREPARING:
|
||||
pauseButton.setVisibility(View.GONE);
|
||||
stopButton.setVisibility(View.VISIBLE);
|
||||
startButton.setVisibility(View.GONE);
|
||||
break;
|
||||
default:
|
||||
pauseButton.setVisibility(View.GONE);
|
||||
stopButton.setVisibility(View.GONE);
|
||||
startButton.setVisibility(View.VISIBLE);
|
||||
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> {
|
||||
public SongListAdapter(List<DownloadFile> entries) {
|
||||
|
|
|
@ -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() {
|
||||
stopService(new Intent(this, DownloadServiceImpl.class));
|
||||
Util.unregisterMediaButtonEventReceiver(this);
|
||||
|
|
|
@ -90,7 +90,8 @@ public final class PlayVideoActivity extends Activity {
|
|||
|
||||
private String getVideoUrl() {
|
||||
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
|
||||
|
|
|
@ -44,13 +44,13 @@ import com.thejoshwa.ultrasonic.androidapp.domain.SearchResult;
|
|||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
||||
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.Constants;
|
||||
import com.thejoshwa.ultrasonic.androidapp.util.EntryAdapter;
|
||||
import com.thejoshwa.ultrasonic.androidapp.util.MergeAdapter;
|
||||
import com.thejoshwa.ultrasonic.androidapp.util.TabActivityBackgroundTask;
|
||||
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.
|
||||
|
@ -190,16 +190,16 @@ public class SearchActivity extends SubsonicTabActivity {
|
|||
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.album_menu_play_now:
|
||||
downloadRecursively(id, false, false, true, false);
|
||||
downloadRecursively(id, false, false, true, false, false);
|
||||
break;
|
||||
case R.id.album_menu_play_next:
|
||||
downloadRecursively(id, false, true, false, true);
|
||||
downloadRecursively(id, false, true, false, true, false);
|
||||
break;
|
||||
case R.id.album_menu_play_last:
|
||||
downloadRecursively(id, false, true, false, false);
|
||||
downloadRecursively(id, false, true, false, false, false);
|
||||
break;
|
||||
case R.id.album_menu_pin:
|
||||
downloadRecursively(id, true, true, false, false);
|
||||
downloadRecursively(id, true, true, false, false, false);
|
||||
break;
|
||||
default:
|
||||
return super.onContextItemSelected(menuItem);
|
||||
|
@ -330,7 +330,7 @@ public class SearchActivity extends SubsonicTabActivity {
|
|||
if (!append) {
|
||||
downloadService.clear();
|
||||
}
|
||||
downloadService.download(Arrays.asList(song), save, false, playNext);
|
||||
downloadService.download(Arrays.asList(song), save, false, playNext, false);
|
||||
if (autoplay) {
|
||||
downloadService.play(downloadService.size() - 1);
|
||||
}
|
||||
|
@ -340,8 +340,10 @@ public class SearchActivity extends SubsonicTabActivity {
|
|||
}
|
||||
|
||||
private void onVideoSelected(MusicDirectory.Entry entry) {
|
||||
int maxBitrate = Util.getMaxVideoBitrate(this);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -42,11 +42,13 @@ import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
|||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
||||
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.TabActivityBackgroundTask;
|
||||
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.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -142,28 +144,26 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
|||
playNowButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
download(false, false, true, false);
|
||||
selectAll(false, false);
|
||||
playNow(false, false);
|
||||
}
|
||||
});
|
||||
playNextButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
download(true, false, false, true);
|
||||
download(true, false, false, true, false);
|
||||
selectAll(false, false);
|
||||
}
|
||||
});
|
||||
playLastButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
download(true, false, false, false);
|
||||
selectAll(false, false);
|
||||
playNow(false, true);
|
||||
}
|
||||
});
|
||||
pinButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
download(true, true, false, false);
|
||||
downloadBackground(true);
|
||||
selectAll(false, false);
|
||||
}
|
||||
});
|
||||
|
@ -233,8 +233,22 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
|||
|
||||
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() {
|
||||
playAll(false, false);
|
||||
}
|
||||
|
||||
private void playAll() {
|
||||
private void playAll(final boolean shuffle, final boolean append) {
|
||||
boolean hasSubFolders = false;
|
||||
for (int i = 0; i < albumListView.getCount(); 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);
|
||||
if (hasSubFolders && id != null) {
|
||||
downloadRecursively(id, false, false, true, false);
|
||||
downloadRecursively(id, false, append, !append, shuffle, false);
|
||||
} else {
|
||||
selectAll(true, false);
|
||||
download(false, false, true, false);
|
||||
download(append, false, !append, false, shuffle);
|
||||
selectAll(false, false);
|
||||
}
|
||||
}
|
||||
|
@ -282,16 +296,16 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
|||
songs.add((MusicDirectory.Entry) albumListView.getItemAtPosition(info.position));
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.album_menu_play_now:
|
||||
downloadRecursively(entry.getId(), false, false, true, false);
|
||||
downloadRecursively(entry.getId(), false, false, true, false, false);
|
||||
break;
|
||||
case R.id.album_menu_play_next:
|
||||
downloadRecursively(entry.getId(), false, false, true, true);
|
||||
downloadRecursively(entry.getId(), false, false, true, true, false);
|
||||
break;
|
||||
case R.id.album_menu_play_last:
|
||||
downloadRecursively(entry.getId(), false, true, false, false);
|
||||
downloadRecursively(entry.getId(), false, true, false, false, false);
|
||||
break;
|
||||
case R.id.album_menu_pin:
|
||||
downloadRecursively(entry.getId(), true, true, false, false);
|
||||
downloadRecursively(entry.getId(), true, true, false, false, false);
|
||||
break;
|
||||
case R.id.select_album_play_all:
|
||||
playAll();
|
||||
|
@ -321,14 +335,14 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
|||
return false;
|
||||
}
|
||||
|
||||
private void getMusicDirectory(final String id, String name) {
|
||||
getActionBar().setSubtitle(name);
|
||||
private void getMusicDirectory(final String id, final String name) {
|
||||
getActionBar().setSubtitle(name);
|
||||
|
||||
new LoadTask() {
|
||||
@Override
|
||||
protected MusicDirectory load(MusicService service) throws Exception {
|
||||
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();
|
||||
}
|
||||
|
@ -393,13 +407,13 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
|||
}.execute();
|
||||
}
|
||||
|
||||
private void getPlaylist(final String playlistId, String playlistName) {
|
||||
getActionBar().setSubtitle(playlistName);
|
||||
private void getPlaylist(final String playlistId, final String playlistName) {
|
||||
getActionBar().setSubtitle(playlistName);
|
||||
|
||||
new LoadTask() {
|
||||
@Override
|
||||
protected MusicDirectory load(MusicService service) throws Exception {
|
||||
return service.getPlaylist(playlistId, SelectAlbumActivity.this, this);
|
||||
return service.getPlaylist(playlistId, playlistName, SelectAlbumActivity.this, this);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
@ -527,7 +541,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
|||
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) {
|
||||
return;
|
||||
}
|
||||
|
@ -541,15 +555,16 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
|||
}
|
||||
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
getDownloadService().download(songs, save, autoplay, playNext);
|
||||
getDownloadService().download(songs, save, autoplay, playNext, shuffle);
|
||||
String playlistName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME);
|
||||
|
||||
if (playlistName != null) {
|
||||
getDownloadService().setSuggestedPlaylistName(playlistName);
|
||||
}
|
||||
|
||||
if (autoplay) {
|
||||
Util.startActivityWithoutTransition(SelectAlbumActivity.this, DownloadActivity.class);
|
||||
if (Util.getShouldTransitionOnPlaybackPreference(SelectAlbumActivity.this)) {
|
||||
Util.startActivityWithoutTransition(SelectAlbumActivity.this, DownloadActivity.class);
|
||||
}
|
||||
} else if (save) {
|
||||
Util.toast(SelectAlbumActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_downloading, songs.size(), songs.size()));
|
||||
} else if (playNext) {
|
||||
|
@ -562,10 +577,42 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
private void delete() {
|
||||
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() {
|
||||
List<MusicDirectory.Entry> songs = getSelectedSongs();
|
||||
if(songs.isEmpty()) {
|
||||
selectAll(true, false);
|
||||
songs = getSelectedSongs();
|
||||
}
|
||||
if (getDownloadService() != null) {
|
||||
getDownloadService().delete(getSelectedSongs());
|
||||
getDownloadService().delete(songs);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -578,11 +625,21 @@ public class SelectAlbumActivity extends SubsonicTabActivity {
|
|||
}
|
||||
|
||||
private void playVideo(MusicDirectory.Entry entry) {
|
||||
int maxBitrate = Util.getMaxVideoBitrate(this);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (licenseValid) {
|
||||
|
|
|
@ -41,11 +41,11 @@ import com.thejoshwa.ultrasonic.androidapp.domain.Indexes;
|
|||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicFolder;
|
||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||
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.Constants;
|
||||
import com.thejoshwa.ultrasonic.androidapp.util.TabActivityBackgroundTask;
|
||||
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
||||
import com.thejoshwa.ultrasonic.androidapp.view.ArtistAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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);
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(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);
|
||||
return musicService.getIndexes(musicFolderId, refresh, SelectArtistActivity.this, this);
|
||||
|
@ -214,16 +214,16 @@ public class SelectArtistActivity extends SubsonicTabActivity implements Adapter
|
|||
if (artist != null) {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.artist_menu_play_now:
|
||||
downloadRecursively(artist.getId(), false, false, true, false);
|
||||
downloadRecursively(artist.getId(), false, false, true, false, false);
|
||||
break;
|
||||
case R.id.artist_menu_play_next:
|
||||
downloadRecursively(artist.getId(), false, false, true, true);
|
||||
downloadRecursively(artist.getId(), false, false, true, true, false);
|
||||
break;
|
||||
case R.id.artist_menu_play_last:
|
||||
downloadRecursively(artist.getId(), false, true, false, false);
|
||||
downloadRecursively(artist.getId(), false, true, false, false, false);
|
||||
break;
|
||||
case R.id.artist_menu_pin:
|
||||
downloadRecursively(artist.getId(), true, true, false, false);
|
||||
downloadRecursively(artist.getId(), true, true, false, false, false);
|
||||
break;
|
||||
default:
|
||||
return super.onContextItemSelected(menuItem);
|
||||
|
|
|
@ -19,15 +19,20 @@
|
|||
|
||||
package com.thejoshwa.ultrasonic.androidapp.activity;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
|
||||
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.service.MusicServiceFactory;
|
||||
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.CacheCleaner;
|
||||
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.Util;
|
||||
import com.thejoshwa.ultrasonic.androidapp.view.PlaylistAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SelectPlaylistActivity extends SubsonicTabActivity implements AdapterView.OnItemClickListener {
|
||||
|
||||
private static final int MENU_ITEM_PLAY_ALL = 1;
|
||||
|
||||
private PullToRefreshListView refreshPlaylistsListView;
|
||||
private ListView playlistsListView;
|
||||
private View emptyTextView;
|
||||
private PlaylistAdapter playlistAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -99,12 +108,16 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
|
|||
protected List<Playlist> doInBackground() throws Throwable {
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(SelectPlaylistActivity.this);
|
||||
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
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
@ -114,7 +127,12 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
|
|||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo 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
|
||||
|
@ -122,14 +140,35 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
|
|||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
|
||||
Playlist playlist = (Playlist) playlistsListView.getItemAtPosition(info.position);
|
||||
|
||||
Intent intent;
|
||||
switch (menuItem.getItemId()) {
|
||||
case MENU_ITEM_PLAY_ALL:
|
||||
Intent intent = new Intent(SelectPlaylistActivity.this, SelectAlbumActivity.class);
|
||||
case R.id.playlist_menu_pin:
|
||||
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_NAME, playlist.getName());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_AUTOPLAY, true);
|
||||
Util.startActivityWithoutTransition(SelectPlaylistActivity.this, intent);
|
||||
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:
|
||||
return super.onContextItemSelected(menuItem);
|
||||
}
|
||||
|
@ -157,17 +196,121 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
|
|||
intent.putExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME, playlist.getName());
|
||||
Util.startActivityWithoutTransition(SelectPlaylistActivity.this, intent);
|
||||
}
|
||||
|
||||
private class PlaylistAdapter extends ArrayAdapter<Playlist> {
|
||||
public PlaylistAdapter(List<Playlist> playlists) {
|
||||
super(SelectPlaylistActivity.this, R.layout.playlist_list_item, playlists);
|
||||
}
|
||||
}
|
||||
|
||||
private class GetDataTask extends AsyncTask<Void, Void, String[]> {
|
||||
private void deletePlaylist(final Playlist playlist) {
|
||||
new AlertDialog.Builder(this)
|
||||
.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[]> {
|
||||
@Override
|
||||
protected void onPostExecute(String[] result) {
|
||||
refreshPlaylistsListView.onRefreshComplete();
|
||||
refreshPlaylistsListView.onRefreshComplete();
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ package com.thejoshwa.ultrasonic.androidapp.activity;
|
|||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.EditTextPreference;
|
||||
|
@ -77,6 +78,7 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer
|
|||
private ListPreference defaultArtists;
|
||||
private CheckBoxPreference mediaButtonsEnabled;
|
||||
private CheckBoxPreference lockScreenEnabled;
|
||||
private CheckBoxPreference gaplessPlaybackEnabled;
|
||||
private int maxServerCount = 10;
|
||||
private int minServerCount = 0;
|
||||
|
||||
|
@ -148,6 +150,7 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer
|
|||
defaultAlbums = (ListPreference) findPreference(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS);
|
||||
mediaButtonsEnabled = (CheckBoxPreference) findPreference(Constants.PREFERENCES_KEY_MEDIA_BUTTONS);
|
||||
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() {
|
||||
@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 );
|
||||
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.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
@ -35,27 +39,32 @@ import android.os.Environment;
|
|||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.Window;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import com.thejoshwa.ultrasonic.androidapp.R;
|
||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory.Entry;
|
||||
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.DownloadService;
|
||||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadServiceImpl;
|
||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||
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.FileUtil;
|
||||
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.SilentBackgroundTask;
|
||||
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
||||
|
||||
import net.simonvt.menudrawer.MenuDrawer;
|
||||
|
@ -67,6 +76,7 @@ import net.simonvt.menudrawer.Position;
|
|||
public class SubsonicTabActivity extends Activity implements OnClickListener{
|
||||
private static final String TAG = SubsonicTabActivity.class.getSimpleName();
|
||||
private static ImageLoader IMAGE_LOADER;
|
||||
protected static String theme;
|
||||
private static SubsonicTabActivity instance;
|
||||
|
||||
private boolean destroyed;
|
||||
|
@ -78,7 +88,7 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
public MenuDrawer menuDrawer;
|
||||
private int activePosition = 1;
|
||||
private int menuActiveViewId;
|
||||
private View nowPlaying = null;
|
||||
private View nowPlayingView = null;
|
||||
View searchMenuItem = null;
|
||||
View playlistsMenuItem = null;
|
||||
View menuMain = null;
|
||||
|
@ -93,7 +103,7 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
startService(new Intent(this, DownloadServiceImpl.class));
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
|
||||
if (bundle != null) {
|
||||
if (bundle != null) {
|
||||
activePosition = bundle.getInt(STATE_ACTIVE_POSITION);
|
||||
menuActiveViewId = bundle.getInt(STATE_ACTIVE_VIEW_ID);
|
||||
}
|
||||
|
@ -120,23 +130,12 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
if (activeView != null) {
|
||||
menuDrawer.setActiveView(activeView);
|
||||
}
|
||||
|
||||
instance = this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle bundle) {
|
||||
super.onPostCreate(bundle);
|
||||
|
||||
if (!nowPlayingHidden) {
|
||||
showNowPlaying();
|
||||
} else {
|
||||
hideNowPlaying();
|
||||
}
|
||||
|
||||
int visibility = Util.isOffline(this) ? View.GONE : View.VISIBLE;
|
||||
searchMenuItem.setVisibility(visibility);
|
||||
playlistsMenuItem.setVisibility(visibility);
|
||||
instance = this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -144,14 +143,19 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
super.onResume();
|
||||
applyTheme();
|
||||
instance = this;
|
||||
|
||||
Util.registerMediaButtonEventReceiver(this);
|
||||
|
||||
// Make sure to update theme
|
||||
if (theme != null && !theme.equals(Util.getTheme(this))) {
|
||||
restart();
|
||||
}
|
||||
|
||||
if (!nowPlayingHidden) {
|
||||
showNowPlaying();
|
||||
} else {
|
||||
hideNowPlaying();
|
||||
}
|
||||
|
||||
Util.registerMediaButtonEventReceiver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -187,6 +191,13 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
|
||||
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
|
||||
public void finish() {
|
||||
|
@ -194,18 +205,22 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
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 (nowPlaying != null) {
|
||||
nowPlaying.setVisibility(View.GONE);
|
||||
if (nowPlayingView != null) {
|
||||
nowPlayingView.setVisibility(View.GONE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (nowPlaying != null) {
|
||||
if (nowPlayingView != null) {
|
||||
final DownloadService downloadService = DownloadServiceImpl.getInstance();
|
||||
|
||||
if (downloadService != null) {
|
||||
|
@ -222,12 +237,12 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
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);
|
||||
nowPlaying.setOnTouchListener(swipeDetector);
|
||||
nowPlayingView.setOnTouchListener(swipeDetector);
|
||||
|
||||
nowPlaying.setOnClickListener(new View.OnClickListener() {
|
||||
nowPlayingView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
}
|
||||
|
@ -253,85 +268,95 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
}
|
||||
}
|
||||
|
||||
public void showNowPlaying(final Context context, final DownloadServiceImpl downloadService, final MusicDirectory.Entry song, PlayerState playerState) {
|
||||
nowPlaying = findViewById(R.id.now_playing);
|
||||
|
||||
if (!Util.getShowNowPlayingPreference(this)) {
|
||||
if (nowPlaying != null) {
|
||||
nowPlaying.setVisibility(View.GONE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (nowPlaying != null) {
|
||||
nowPlaying.setVisibility(View.VISIBLE);
|
||||
nowPlayingHidden = false;
|
||||
|
||||
String title = song.getTitle();
|
||||
String artist = song.getArtist();
|
||||
|
||||
try {
|
||||
ImageView nowPlayingImage = (ImageView) nowPlaying.findViewById(R.id.now_playing_image);
|
||||
TextView nowPlayingTrack = (TextView) nowPlaying.findViewById(R.id.now_playing_trackname);
|
||||
TextView nowPlayingArtist = (TextView) nowPlaying.findViewById(R.id.now_playing_artist);
|
||||
private void showNowPlaying(final Context context, final DownloadServiceImpl downloadService, final MusicDirectory.Entry song, final PlayerState playerState) {
|
||||
this.runOnUiThread( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
nowPlayingView = findViewById(R.id.now_playing);
|
||||
|
||||
if (!Util.getShowNowPlayingPreference(SubsonicTabActivity.this)) {
|
||||
if (nowPlayingView != null) {
|
||||
nowPlayingView.setVisibility(View.GONE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (nowPlayingView != null) {
|
||||
nowPlayingView.setVisibility(View.VISIBLE);
|
||||
nowPlayingHidden = false;
|
||||
|
||||
String title = song.getTitle();
|
||||
String artist = song.getArtist();
|
||||
|
||||
try {
|
||||
ImageView nowPlayingImage = (ImageView) nowPlayingView.findViewById(R.id.now_playing_image);
|
||||
TextView nowPlayingTrack = (TextView) nowPlayingView.findViewById(R.id.now_playing_trackname);
|
||||
TextView nowPlayingArtist = (TextView) nowPlayingView.findViewById(R.id.now_playing_artist);
|
||||
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
int imageSizeLarge = (int) Math.round(Math.min(metrics.widthPixels, metrics.heightPixels));
|
||||
|
||||
int size = 64;
|
||||
|
||||
if (imageSizeLarge <= 480) {
|
||||
size = 64;
|
||||
} else if (imageSizeLarge <= 768) {
|
||||
size = 128;
|
||||
} else if (imageSizeLarge <= 1024) {
|
||||
size = 256;
|
||||
} else if (imageSizeLarge <= 1080) {
|
||||
size = imageSizeLarge;
|
||||
}
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
int imageSizeLarge = (int) Math.round(Math.min(metrics.widthPixels, metrics.heightPixels));
|
||||
|
||||
int size = 64;
|
||||
|
||||
if (imageSizeLarge <= 480) {
|
||||
size = 64;
|
||||
} else if (imageSizeLarge <= 768) {
|
||||
size = 128;
|
||||
} else if (imageSizeLarge <= 1024) {
|
||||
size = 256;
|
||||
} else if (imageSizeLarge <= 1080) {
|
||||
size = imageSizeLarge;
|
||||
}
|
||||
|
||||
Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, song, size);
|
||||
Bitmap bitmap = FileUtil.getAlbumArtBitmap(context, song, size);
|
||||
|
||||
if (bitmap == null) {
|
||||
// set default album art
|
||||
nowPlayingImage.setImageResource(R.drawable.unknown_album);
|
||||
} else {
|
||||
nowPlayingImage.setImageBitmap(bitmap);
|
||||
}
|
||||
|
||||
nowPlayingImage.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(SubsonicTabActivity.this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, song.getParent());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, song.getAlbum());
|
||||
Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent);
|
||||
}
|
||||
});
|
||||
|
||||
nowPlayingTrack.setText(title);
|
||||
nowPlayingArtist.setText(artist);
|
||||
if (bitmap == null) {
|
||||
// set default album art
|
||||
nowPlayingImage.setImageResource(R.drawable.unknown_album);
|
||||
} else {
|
||||
nowPlayingImage.setImageBitmap(bitmap);
|
||||
}
|
||||
|
||||
nowPlayingImage.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(SubsonicTabActivity.this, SelectAlbumActivity.class);
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, song.getParent());
|
||||
intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, song.getAlbum());
|
||||
Util.startActivityWithoutTransition(SubsonicTabActivity.this, intent);
|
||||
}
|
||||
});
|
||||
|
||||
nowPlayingTrack.setText(title);
|
||||
nowPlayingArtist.setText(artist);
|
||||
|
||||
} catch (Exception x) {
|
||||
Log.w(TAG, "Failed to get notification cover art", x);
|
||||
}
|
||||
} catch (Exception 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) {
|
||||
playButton.setImageDrawable(Util.getDrawableFromAttribute(this, R.attr.media_play));
|
||||
} else if (playerState == PlayerState.STARTED) {
|
||||
playButton.setImageDrawable(Util.getDrawableFromAttribute(this, R.attr.media_pause));
|
||||
}
|
||||
}
|
||||
if (playerState == PlayerState.PAUSED) {
|
||||
playButton.setImageDrawable(Util.getDrawableFromAttribute(SubsonicTabActivity.this, R.attr.media_play));
|
||||
} else if (playerState == PlayerState.STARTED) {
|
||||
playButton.setImageDrawable(Util.getDrawableFromAttribute(SubsonicTabActivity.this, R.attr.media_pause));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
nowPlaying.setVisibility(View.GONE);
|
||||
}
|
||||
if (nowPlayingView != null) {
|
||||
nowPlayingView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static SubsonicTabActivity getInstance() {
|
||||
|
@ -384,16 +409,32 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
}
|
||||
return IMAGE_LOADER;
|
||||
}
|
||||
|
||||
public synchronized static ImageLoader getStaticImageLoader(Context context) {
|
||||
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 playNext) {
|
||||
ModalBackgroundTask<List<MusicDirectory.Entry>> task = new ModalBackgroundTask<List<MusicDirectory.Entry>>(this, false) {
|
||||
|
||||
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;
|
||||
|
||||
@Override
|
||||
protected List<MusicDirectory.Entry> doInBackground() throws Throwable {
|
||||
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>();
|
||||
getSongsRecursively(root, songs);
|
||||
return songs;
|
||||
|
@ -411,7 +452,7 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
}
|
||||
for (MusicDirectory.Entry dir : parent.getChildren(true, false)) {
|
||||
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,18 +460,98 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
protected void done(List<MusicDirectory.Entry> songs) {
|
||||
DownloadService downloadService = getDownloadService();
|
||||
if (!songs.isEmpty() && downloadService != null) {
|
||||
if (!append && !playNext) {
|
||||
if (!append) {
|
||||
downloadService.clear();
|
||||
}
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
downloadService.download(songs, save, autoplay, playNext);
|
||||
Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class);
|
||||
if(!background) {
|
||||
downloadService.download(songs, save, autoplay, false, shuffle);
|
||||
if (!append && Util.getShouldTransitionOnPlaybackPreference(SubsonicTabActivity.this)) {
|
||||
Util.startActivityWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class);
|
||||
}
|
||||
}
|
||||
else {
|
||||
downloadService.downloadBackground(songs, save);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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() {
|
||||
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
|
@ -481,7 +602,6 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
//menuDrawer.setActiveView(v);
|
||||
menuActiveViewId = v.getId();
|
||||
|
||||
Intent intent;
|
||||
|
@ -619,5 +739,4 @@ public class SubsonicTabActivity extends Activity implements OnClickListener{
|
|||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -38,6 +38,8 @@ public class EqualizerController {
|
|||
|
||||
private final Context context;
|
||||
private Equalizer equalizer;
|
||||
private boolean released = false;
|
||||
private int audioSessionId = 0;
|
||||
|
||||
// Class initialization fails when this throws an exception.
|
||||
static {
|
||||
|
@ -58,7 +60,8 @@ public class EqualizerController {
|
|||
public EqualizerController(Context context, MediaPlayer mediaPlayer) {
|
||||
this.context = context;
|
||||
try {
|
||||
equalizer = new Equalizer(0, mediaPlayer.getAudioSessionId());
|
||||
audioSessionId = mediaPlayer.getAudioSessionId();
|
||||
equalizer = new Equalizer(0, audioSessionId);
|
||||
} catch (Throwable x) {
|
||||
Log.w(TAG, "Failed to create equalizer.", x);
|
||||
}
|
||||
|
@ -97,11 +100,21 @@ public class EqualizerController {
|
|||
|
||||
public void release() {
|
||||
if (isAvailable()) {
|
||||
released = true;
|
||||
equalizer.release();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -110,7 +123,7 @@ public class EqualizerController {
|
|||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1477565229521401809L;
|
||||
private static final long serialVersionUID = 626565082425206061L;
|
||||
private final short[] bandLevels;
|
||||
private short preset;
|
||||
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 Visualizer visualizer;
|
||||
private boolean released = false;
|
||||
private int audioSessionId = 0;
|
||||
|
||||
// Class initialization fails when this throws an exception.
|
||||
static {
|
||||
|
@ -54,7 +56,8 @@ public class VisualizerController {
|
|||
|
||||
public VisualizerController(Context context, MediaPlayer mediaPlayer) {
|
||||
try {
|
||||
visualizer = new Visualizer(mediaPlayer.getAudioSessionId());
|
||||
audioSessionId = mediaPlayer.getAudioSessionId();
|
||||
visualizer = new Visualizer(audioSessionId);
|
||||
} catch (Throwable x) {
|
||||
Log.w(TAG, "Failed to create visualizer.", x);
|
||||
}
|
||||
|
@ -78,11 +81,21 @@ public class VisualizerController {
|
|||
public void release() {
|
||||
if (isAvailable()) {
|
||||
visualizer.release();
|
||||
released = true;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -32,6 +32,7 @@ public class Artist implements Serializable {
|
|||
private String id;
|
||||
private String name;
|
||||
private String index;
|
||||
private int closeness;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
@ -56,6 +57,14 @@ public class Artist implements Serializable {
|
|||
public void setIndex(String index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getCloseness() {
|
||||
return closeness;
|
||||
}
|
||||
|
||||
public void setCloseness(int closeness) {
|
||||
this.closeness = closeness;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
package com.thejoshwa.ultrasonic.androidapp.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
@ -29,26 +30,26 @@ public class Indexes implements Serializable {
|
|||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -9186563899711652646L;
|
||||
private static final long serialVersionUID = 8156117238598414701L;
|
||||
private final long lastModified;
|
||||
private final List<Artist> shortcuts;
|
||||
private final List<Artist> artists;
|
||||
private final List<com.thejoshwa.ultrasonic.androidapp.domain.Artist> shortcuts;
|
||||
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.shortcuts = shortcuts;
|
||||
this.artists = artists;
|
||||
}
|
||||
|
||||
public long getLastModified() {
|
||||
public long getLastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
public List<Artist> getShortcuts() {
|
||||
public List<com.thejoshwa.ultrasonic.androidapp.domain.Artist> getShortcuts() {
|
||||
return shortcuts;
|
||||
}
|
||||
|
||||
public List<Artist> getArtists() {
|
||||
public List<com.thejoshwa.ultrasonic.androidapp.domain.Artist> getArtists() {
|
||||
return artists;
|
||||
}
|
||||
}
|
|
@ -64,6 +64,10 @@ public class MusicDirectory {
|
|||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -3339106650010798108L;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String id;
|
||||
private String parent;
|
||||
private boolean directory;
|
||||
|
@ -85,6 +89,7 @@ public class MusicDirectory {
|
|||
private boolean video;
|
||||
private boolean starred;
|
||||
private Integer discNumber;
|
||||
private int closeness;
|
||||
|
||||
public Integer getDiscNumber() {
|
||||
return discNumber;
|
||||
|
@ -257,6 +262,14 @@ public class MusicDirectory {
|
|||
public void setVideo(boolean video) {
|
||||
this.video = video;
|
||||
}
|
||||
|
||||
public int getCloseness() {
|
||||
return closeness;
|
||||
}
|
||||
|
||||
public void setCloseness(int closeness) {
|
||||
this.closeness = closeness;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object 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 name;
|
||||
private String owner;
|
||||
private String comment;
|
||||
private String songCount;
|
||||
private String created;
|
||||
private Boolean pub;
|
||||
|
||||
public Playlist(String id, String name) {
|
||||
this.id = id;
|
||||
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() {
|
||||
return id;
|
||||
|
@ -52,6 +66,45 @@ public class Playlist implements Serializable {
|
|||
public void setName(String 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
|
||||
public String toString() {
|
||||
|
|
|
@ -54,7 +54,7 @@ public class CachedMusicService implements MusicService {
|
|||
private final LRUCache<String, TimeLimitedCache<MusicDirectory>> cachedMusicDirectories;
|
||||
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<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<Genre>> cachedGenres = new TimeLimitedCache<List<Genre>>(10 * 3600, TimeUnit.SECONDS);
|
||||
private String restUrl;
|
||||
|
@ -82,11 +82,14 @@ public class CachedMusicService implements MusicService {
|
|||
}
|
||||
|
||||
@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);
|
||||
if (refresh) {
|
||||
cachedMusicFolders.clear();
|
||||
}
|
||||
List<MusicFolder> result = cachedMusicFolders.get();
|
||||
if (result == null) {
|
||||
result = musicService.getMusicFolders(context, progressListener);
|
||||
result = musicService.getMusicFolders(refresh, context, progressListener);
|
||||
cachedMusicFolders.set(result);
|
||||
}
|
||||
return result;
|
||||
|
@ -109,12 +112,12 @@ public class CachedMusicService implements MusicService {
|
|||
}
|
||||
|
||||
@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);
|
||||
TimeLimitedCache<MusicDirectory> cache = refresh ? null : cachedMusicDirectories.get(id);
|
||||
MusicDirectory dir = cache == null ? null : cache.get();
|
||||
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.set(dir);
|
||||
cachedMusicDirectories.put(id, cache);
|
||||
|
@ -128,8 +131,8 @@ public class CachedMusicService implements MusicService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||
return musicService.getPlaylist(id, context, progressListener);
|
||||
public MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception {
|
||||
return musicService.getPlaylist(id, name, context, progressListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -145,9 +148,30 @@ public class CachedMusicService implements MusicService {
|
|||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@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
|
||||
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
|
||||
return musicService.getLyrics(artist, title, context, progressListener);
|
||||
|
@ -165,7 +189,7 @@ public class CachedMusicService implements MusicService {
|
|||
|
||||
@Override
|
||||
public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception {
|
||||
return musicService.getRandomSongs(size, context, progressListener);
|
||||
return musicService.getRandomSongs(size, context, progressListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -194,8 +218,13 @@ public class CachedMusicService implements MusicService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getVideoUrl(Context context, String id) {
|
||||
return musicService.getVideoUrl(context, id);
|
||||
public String getVideoUrl(int maxBitrate, Context context, String id) {
|
||||
return musicService.getVideoUrl(maxBitrate, context, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVideoStreamUrl(int maxBitrate, Context context, String id) {
|
||||
return musicService.getVideoStreamUrl(maxBitrate, context, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.io.InputStream;
|
|||
import java.io.OutputStream;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.PowerManager;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
|
@ -56,6 +57,9 @@ public class DownloadFile {
|
|||
private boolean save;
|
||||
private boolean failed;
|
||||
private int bitRate;
|
||||
private boolean isPlaying = false;
|
||||
private boolean saveWhenDone = false;
|
||||
private boolean completeWhenDone = false;
|
||||
|
||||
public DownloadFile(Context context, MusicDirectory.Entry song, boolean save) {
|
||||
this.context = context;
|
||||
|
@ -63,40 +67,36 @@ public class DownloadFile {
|
|||
this.save = save;
|
||||
saveFile = FileUtil.getSongFile(context, song);
|
||||
bitRate = Util.getMaxBitrate(context);
|
||||
partialFile = new File(saveFile.getParent(), FileUtil.getBaseName(saveFile.getName()) + "." + bitRate + ".partial." + FileUtil.getExtension(saveFile.getName()));
|
||||
completeFile = new File(saveFile.getParent(), FileUtil.getBaseName(saveFile.getName()) + ".complete." + FileUtil.getExtension(saveFile.getName()));
|
||||
partialFile = new File(saveFile.getParent(), FileUtil.getBaseName(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);
|
||||
}
|
||||
|
||||
public MusicDirectory.Entry getSong() {
|
||||
return song;
|
||||
}
|
||||
|
||||
public boolean isOffline() {
|
||||
return Util.isOffline(context);
|
||||
}
|
||||
|
||||
public boolean isStreamProxyEnabled() {
|
||||
return Util.isStreamProxyEnabled(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effective bit rate.
|
||||
*/
|
||||
public int getBitRate() {
|
||||
if(!partialFile.exists()) {
|
||||
bitRate = Util.getMaxBitrate(context);
|
||||
}
|
||||
if (bitRate > 0) {
|
||||
return bitRate;
|
||||
}
|
||||
return song.getBitRate() == null ? 160 : song.getBitRate();
|
||||
}
|
||||
|
||||
public int getBufferLength() {
|
||||
return Util.getBufferLength(this.context);
|
||||
}
|
||||
|
||||
public synchronized void download() {
|
||||
FileUtil.createDirectoryForParent(saveFile);
|
||||
failed = false;
|
||||
if(!partialFile.exists()) {
|
||||
bitRate = Util.getMaxBitrate(context);
|
||||
}
|
||||
downloadTask = new DownloadTask();
|
||||
downloadTask.start();
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ public class DownloadFile {
|
|||
}
|
||||
|
||||
public synchronized boolean isWorkDone() {
|
||||
return saveFile.exists() || (completeFile.exists() && !save);
|
||||
return saveFile.exists() || (completeFile.exists() && !save) || saveWhenDone || completeWhenDone;
|
||||
}
|
||||
|
||||
public synchronized boolean isDownloading() {
|
||||
|
@ -191,6 +191,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
|
||||
public String toString() {
|
||||
|
@ -205,7 +229,7 @@ public class DownloadFile {
|
|||
InputStream in = null;
|
||||
FileOutputStream out = null;
|
||||
PowerManager.WakeLock wakeLock = null;
|
||||
|
||||
WifiManager.WifiLock wifiLock = null;
|
||||
try {
|
||||
|
||||
if (Util.isScreenLitOnDownload(context)) {
|
||||
|
@ -214,52 +238,68 @@ public class DownloadFile {
|
|||
wakeLock.acquire();
|
||||
Log.i(TAG, "Acquired wake lock " + wakeLock);
|
||||
}
|
||||
|
||||
wifiLock = Util.createWifiLock(context, toString());
|
||||
wifiLock.acquire();
|
||||
|
||||
if (saveFile.exists() && saveFile.length() == song.getSize()) {
|
||||
if (saveFile.exists()) {
|
||||
Log.i(TAG, saveFile + " already exists. Skipping.");
|
||||
return;
|
||||
}
|
||||
if (completeFile.exists() && completeFile.length() == song.getSize()) {
|
||||
if (completeFile.exists()) {
|
||||
if (save) {
|
||||
Util.atomicCopy(completeFile, saveFile);
|
||||
if(isPlaying) {
|
||||
saveWhenDone = true;
|
||||
} else {
|
||||
Util.renameFile(completeFile, saveFile);
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, completeFile + " already exists. Skipping.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isOffline()) {
|
||||
Log.i(TAG, "We are offline. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(context);
|
||||
|
||||
// Attempt partial HTTP GET, appending to the file if it exists.
|
||||
HttpResponse response = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this);
|
||||
in = response.getEntity().getContent();
|
||||
boolean partial = response.getStatusLine().getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT;
|
||||
if (partial) {
|
||||
Log.i(TAG, "Executed partial HTTP GET, skipping " + partialFile.length() + " bytes");
|
||||
}
|
||||
// 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.
|
||||
HttpResponse response = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this);
|
||||
in = response.getEntity().getContent();
|
||||
boolean partial = response.getStatusLine().getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT;
|
||||
if (partial) {
|
||||
Log.i(TAG, "Executed partial HTTP GET, skipping " + partialFile.length() + " bytes");
|
||||
}
|
||||
|
||||
out = new FileOutputStream(partialFile, partial);
|
||||
long n = copy(in, out);
|
||||
Log.i(TAG, "Downloaded " + n + " bytes to " + partialFile);
|
||||
out.flush();
|
||||
out.close();
|
||||
out = new FileOutputStream(partialFile, partial);
|
||||
long n = copy(in, out);
|
||||
Log.i(TAG, "Downloaded " + n + " bytes to " + partialFile);
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
if (isCancelled()) {
|
||||
throw new Exception("Download of '" + song + "' was cancelled");
|
||||
}
|
||||
if (isCancelled()) {
|
||||
throw new Exception("Download of '" + song + "' was cancelled");
|
||||
}
|
||||
|
||||
downloadAndSaveCoverArt(musicService);
|
||||
downloadAndSaveCoverArt(musicService);
|
||||
}
|
||||
|
||||
if (save) {
|
||||
Util.atomicCopy(partialFile, saveFile);
|
||||
mediaStoreService.saveInMediaStore(DownloadFile.this);
|
||||
} else {
|
||||
Util.atomicCopy(partialFile, completeFile);
|
||||
}
|
||||
if(isPlaying) {
|
||||
completeWhenDone = true;
|
||||
} else {
|
||||
if(save) {
|
||||
Util.renameFile(partialFile, saveFile);
|
||||
mediaStoreService.saveInMediaStore(DownloadFile.this);
|
||||
} else {
|
||||
Util.renameFile(partialFile, completeFile);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception x) {
|
||||
Util.close(out);
|
||||
|
@ -277,7 +317,13 @@ public class DownloadFile {
|
|||
wakeLock.release();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -336,4 +382,4 @@ public class DownloadFile {
|
|||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,7 +32,9 @@ import com.thejoshwa.ultrasonic.androidapp.domain.RepeatMode;
|
|||
*/
|
||||
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);
|
||||
|
||||
|
@ -49,18 +51,30 @@ public interface DownloadService {
|
|||
void setKeepScreenOn(boolean screenOn);
|
||||
|
||||
boolean getShowVisualization();
|
||||
|
||||
boolean getEqualizerAvailable();
|
||||
|
||||
boolean getVisualizerAvailable();
|
||||
|
||||
void setShowVisualization(boolean showVisualization);
|
||||
|
||||
void clear();
|
||||
|
||||
void clearBackground();
|
||||
|
||||
void clearIncomplete();
|
||||
|
||||
int size();
|
||||
|
||||
void remove(int which);
|
||||
|
||||
void remove(DownloadFile downloadFile);
|
||||
|
||||
List<DownloadFile> getSongs();
|
||||
|
||||
List<DownloadFile> getDownloads();
|
||||
|
||||
List<DownloadFile> getBackgroundDownloads();
|
||||
|
||||
int getCurrentPlayingIndex();
|
||||
|
||||
|
@ -113,4 +127,8 @@ public interface DownloadService {
|
|||
void adjustJukeboxVolume(boolean up);
|
||||
|
||||
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.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.RemoteControlClient;
|
||||
import android.os.AsyncTask;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
@ -53,6 +56,7 @@ public class DownloadServiceLifecycleSupport {
|
|||
private BroadcastReceiver ejectEventReceiver;
|
||||
private PhoneStateListener phoneStateListener;
|
||||
private boolean externalStorageAvailable= true;
|
||||
private ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
* 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)) {
|
||||
downloadService.pause();
|
||||
} else if (DownloadServiceImpl.CMD_STOP.equals(action)) {
|
||||
downloadService.stop();
|
||||
downloadService.pause();
|
||||
downloadService.seekTo(0);
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +100,7 @@ public class DownloadServiceLifecycleSupport {
|
|||
}
|
||||
};
|
||||
|
||||
executorService = Executors.newScheduledThreadPool(2);
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
executorService.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS);
|
||||
|
||||
// Pause when headset is unplugged.
|
||||
|
@ -105,7 +109,9 @@ public class DownloadServiceLifecycleSupport {
|
|||
public void onReceive(Context context, Intent intent) {
|
||||
Log.i(TAG, "Headset event for: " + intent.getExtras().get("name"));
|
||||
if (intent.getExtras().getInt("state") == 0) {
|
||||
downloadService.pause();
|
||||
if(!downloadService.isJukeboxEnabled()) {
|
||||
downloadService.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -163,12 +169,11 @@ public class DownloadServiceLifecycleSupport {
|
|||
|
||||
public void onDestroy() {
|
||||
executorService.shutdown();
|
||||
serializeDownloadQueue();
|
||||
serializeDownloadQueueNow();
|
||||
downloadService.clear(false);
|
||||
downloadService.unregisterReceiver(ejectEventReceiver);
|
||||
downloadService.unregisterReceiver(headsetEventReceiver);
|
||||
downloadService.unregisterReceiver(intentReceiver);
|
||||
Util.unregisterMediaButtonEventReceiver(downloadService);
|
||||
|
||||
TelephonyManager telephonyManager = (TelephonyManager) downloadService.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
|
||||
|
@ -179,18 +184,26 @@ public class DownloadServiceLifecycleSupport {
|
|||
}
|
||||
|
||||
public void serializeDownloadQueue() {
|
||||
State state = new State();
|
||||
for (DownloadFile downloadFile : downloadService.getDownloads()) {
|
||||
state.songs.add(downloadFile.getSong());
|
||||
}
|
||||
state.currentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
||||
state.currentPlayingPosition = downloadService.getPlayerPosition();
|
||||
new SerializeTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
public void serializeDownloadQueueNow() {
|
||||
List<DownloadFile> songs = new ArrayList<DownloadFile>(downloadService.getSongs());
|
||||
State state = new State();
|
||||
for (DownloadFile downloadFile : songs) {
|
||||
state.songs.add(downloadFile.getSong());
|
||||
}
|
||||
state.currentPlayingIndex = downloadService.getCurrentPlayingIndex();
|
||||
state.currentPlayingPosition = downloadService.getPlayerPosition();
|
||||
|
||||
Log.i(TAG, "Serialized currentPlayingIndex: " + state.currentPlayingIndex + ", currentPlayingPosition: " + state.currentPlayingPosition);
|
||||
FileUtil.serialize(downloadService, state, FILENAME_DOWNLOADS_SER);
|
||||
Log.i(TAG, "Serialized currentPlayingIndex: " + state.currentPlayingIndex + ", currentPlayingPosition: " + state.currentPlayingPosition);
|
||||
FileUtil.serialize(downloadService, state, FILENAME_DOWNLOADS_SER);
|
||||
}
|
||||
|
||||
private void deserializeDownloadQueue() {
|
||||
private void deserializeDownloadQueue() {
|
||||
new DeserializeTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
private void deserializeDownloadQueueNow() {
|
||||
State state = FileUtil.deserialize(downloadService, FILENAME_DOWNLOADS_SER);
|
||||
if (state == null) {
|
||||
return;
|
||||
|
@ -200,33 +213,37 @@ public class DownloadServiceLifecycleSupport {
|
|||
|
||||
// Work-around: Serialize again, as the restore() method creates a serialization without current playing info.
|
||||
serializeDownloadQueue();
|
||||
|
||||
downloadService.setPlayerState(PlayerState.STOPPED);
|
||||
}
|
||||
|
||||
|
||||
private void handleKeyEvent(KeyEvent event) {
|
||||
if (event.getAction() != KeyEvent.ACTION_DOWN || event.getRepeatCount() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.getKeyCode()) {
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
||||
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||
downloadService.togglePlayPause();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
|
||||
downloadService.previous();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_NEXT:
|
||||
if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1) {
|
||||
downloadService.next();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_STOP:
|
||||
downloadService.reset();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||
downloadService.togglePlayPause();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
|
||||
downloadService.previous();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_NEXT:
|
||||
if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1) {
|
||||
downloadService.next();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_STOP:
|
||||
downloadService.stop();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
||||
if(downloadService.getPlayerState() != PlayerState.STARTED) {
|
||||
downloadService.start();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
||||
downloadService.pause();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -244,7 +261,7 @@ public class DownloadServiceLifecycleSupport {
|
|||
switch (state) {
|
||||
case TelephonyManager.CALL_STATE_RINGING:
|
||||
case TelephonyManager.CALL_STATE_OFFHOOK:
|
||||
if (downloadService.getPlayerState() == PlayerState.STARTED) {
|
||||
if (downloadService.getPlayerState() == PlayerState.STARTED && !downloadService.isJukeboxEnabled()) {
|
||||
resumeAfterCall = true;
|
||||
downloadService.pause();
|
||||
}
|
||||
|
@ -268,4 +285,30 @@ public class DownloadServiceLifecycleSupport {
|
|||
private int currentPlayingIndex;
|
||||
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;
|
||||
|
||||
List<MusicFolder> getMusicFolders(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 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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -18,11 +18,15 @@
|
|||
*/
|
||||
package com.thejoshwa.ultrasonic.androidapp.service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -54,7 +58,8 @@ import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
|||
* @author Sindre Mehus
|
||||
*/
|
||||
public class OfflineMusicService extends RESTMusicService {
|
||||
|
||||
private static final String TAG = OfflineMusicService.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public boolean isLicenseValid(Context context, ProgressListener progressListener) throws Exception {
|
||||
return true;
|
||||
|
@ -86,14 +91,14 @@ public class OfflineMusicService extends RESTMusicService {
|
|||
}
|
||||
|
||||
@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);
|
||||
MusicDirectory result = new MusicDirectory();
|
||||
result.setName(dir.getName());
|
||||
|
||||
Set<String> names = new HashSet<String>();
|
||||
|
||||
for (File file : FileUtil.listMusicFiles(dir)) {
|
||||
for (File file : FileUtil.listMediaFiles(dir)) {
|
||||
String name = getName(file);
|
||||
if (name != null & !names.contains(name)) {
|
||||
names.add(name);
|
||||
|
@ -253,15 +258,11 @@ public class OfflineMusicService extends RESTMusicService {
|
|||
|
||||
@Override
|
||||
public Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, ProgressListener progressListener) throws Exception {
|
||||
InputStream in = new FileInputStream(entry.getCoverArt());
|
||||
try {
|
||||
byte[] bytes = Util.toByteArray(in);
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
||||
Log.i("getCoverArt", "getCoverArt");
|
||||
return Util.scaleBitmap(bitmap, size);
|
||||
} finally {
|
||||
Util.close(in);
|
||||
}
|
||||
try {
|
||||
return FileUtil.getAlbumArtBitmap(context, entry, size);
|
||||
} catch(Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -275,29 +276,210 @@ public class OfflineMusicService extends RESTMusicService {
|
|||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
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
|
||||
public MusicDirectory getPlaylist(String id, Context context, ProgressListener progressListener) throws Exception {
|
||||
throw new OfflineException("Playlists not available in offline mode");
|
||||
public MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception {
|
||||
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
|
||||
public void createPlaylist(String id, String name, List<MusicDirectory.Entry> entries, Context context, ProgressListener progressListener) throws Exception {
|
||||
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
|
||||
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
|
||||
|
@ -315,7 +497,12 @@ public class OfflineMusicService extends RESTMusicService {
|
|||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
@ -384,7 +571,7 @@ public class OfflineMusicService extends RESTMusicService {
|
|||
}
|
||||
|
||||
private void listFilesRecursively(File parent, List<File> children) {
|
||||
for (File file : FileUtil.listMusicFiles(parent)) {
|
||||
for (File file : FileUtil.listMediaFiles(parent)) {
|
||||
if (file.isFile()) {
|
||||
children.add(file);
|
||||
} else {
|
||||
|
|
|
@ -18,7 +18,10 @@
|
|||
*/
|
||||
package com.thejoshwa.ultrasonic.androidapp.service;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
@ -174,13 +177,6 @@ public class RESTMusicService implements MusicService {
|
|||
Reader reader = getReader(context, progressListener, "ping", null);
|
||||
try {
|
||||
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 {
|
||||
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);
|
||||
try {
|
||||
return new MusicFoldersParser(context).parse(reader, progressListener);
|
||||
List<MusicFolder> musicFolders = new MusicFoldersParser(context).parse(reader, progressListener);
|
||||
writeCachedMusicFolders(context, musicFolders);
|
||||
return musicFolders;
|
||||
} finally {
|
||||
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 {
|
||||
Reader reader = getReader(context, progressListener, "star", null, "id", id);
|
||||
try {
|
||||
|
@ -225,54 +297,10 @@ public class RESTMusicService implements MusicService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Indexes getIndexes(String musicFolderId, 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 {
|
||||
public MusicDirectory getMusicDirectory(String id, String name, boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
Reader reader = getReader(context, progressListener, "getMusicDirectory", null, "id", id);
|
||||
try {
|
||||
return new MusicDirectoryParser(context).parse(reader, progressListener);
|
||||
return new MusicDirectoryParser(context).parse(name, reader, progressListener);
|
||||
} finally {
|
||||
Util.close(reader);
|
||||
}
|
||||
|
@ -320,18 +348,41 @@ public class RESTMusicService implements MusicService {
|
|||
}
|
||||
|
||||
@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();
|
||||
HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_PLAYLIST);
|
||||
|
||||
Reader reader = getReader(context, progressListener, "getPlaylist", params, "id", id);
|
||||
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 {
|
||||
Util.close(reader);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Playlist> getPlaylists(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
|
||||
Reader reader = getReader(context, progressListener, "getPlaylists", null);
|
||||
|
@ -367,6 +418,65 @@ public class RESTMusicService implements MusicService {
|
|||
Util.close(reader);
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
public Lyrics getLyrics(String artist, String title, Context context, ProgressListener progressListener) throws Exception {
|
||||
|
@ -404,8 +514,14 @@ public class RESTMusicService implements MusicService {
|
|||
public MusicDirectory getRandomSongs(int size, Context context, ProgressListener progressListener) throws Exception {
|
||||
HttpParams params = new BasicHttpParams();
|
||||
HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS);
|
||||
|
||||
List<String> names = new ArrayList<String>();
|
||||
List<Object> values = new ArrayList<Object>();
|
||||
|
||||
Reader reader = getReader(context, progressListener, "getRandomSongs", params, "size", size);
|
||||
names.add("size");
|
||||
values.add(size);
|
||||
|
||||
Reader reader = getReader(context, progressListener, "getRandomSongs", params, names, values);
|
||||
try {
|
||||
return new RandomSongsParser(context).parse(reader, progressListener);
|
||||
} finally {
|
||||
|
@ -487,6 +603,17 @@ public class RESTMusicService implements MusicService {
|
|||
|
||||
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 (!serverScaling || saveToFile) {
|
||||
OutputStream out = null;
|
||||
|
@ -545,16 +672,27 @@ public class RESTMusicService implements MusicService {
|
|||
}
|
||||
|
||||
@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"));
|
||||
builder.append("&id=").append(id);
|
||||
builder.append("&maxBitRate=500");
|
||||
builder.append("&maxBitRate=").append(maxBitrate);
|
||||
builder.append("&autoplay=true");
|
||||
|
||||
String url = rewriteUrlWithRedirect(context, builder.toString());
|
||||
Log.i(TAG, "Using video URL: " + 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
|
||||
public JukeboxStatus updateJukeboxPlaylist(List<String> ids, Context context, ProgressListener progressListener) throws Exception {
|
||||
|
@ -598,7 +736,6 @@ public class RESTMusicService implements MusicService {
|
|||
List<String> parameterNames = Arrays.asList("action", "gain");
|
||||
List<Object> parameterValues = Arrays.<Object>asList("setGain", gain);
|
||||
return executeJukeboxCommand(context, progressListener, parameterNames, parameterValues);
|
||||
|
||||
}
|
||||
|
||||
private JukeboxStatus executeJukeboxCommand(Context context, ProgressListener progressListener, List<String> parameterNames, List<Object> parameterValues) throws Exception {
|
||||
|
@ -631,116 +768,124 @@ public class RESTMusicService implements MusicService {
|
|||
return getReaderForURL(context, url, requestParams, parameterNames, parameterValues, progressListener);
|
||||
}
|
||||
|
||||
private Reader getReaderForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames,
|
||||
List<Object> parameterValues, ProgressListener progressListener) throws Exception {
|
||||
HttpEntity entity = getEntityForURL(context, url, requestParams, parameterNames, parameterValues, progressListener);
|
||||
if (entity == null) {
|
||||
throw new RuntimeException("No entity received for URL " + url);
|
||||
}
|
||||
private Reader getReaderForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener) throws Exception {
|
||||
HttpEntity entity = getEntityForURL(context, url, requestParams, parameterNames, parameterValues, progressListener);
|
||||
if (entity == null) {
|
||||
throw new RuntimeException("No entity received for URL " + url);
|
||||
}
|
||||
|
||||
InputStream in = entity.getContent();
|
||||
return new InputStreamReader(in, Constants.UTF_8);
|
||||
}
|
||||
InputStream in = entity.getContent();
|
||||
return new InputStreamReader(in, Constants.UTF_8);
|
||||
}
|
||||
|
||||
private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames,
|
||||
List<Object> parameterValues, ProgressListener progressListener) throws Exception {
|
||||
return getResponseForURL(context, url, requestParams, parameterNames, parameterValues, null, progressListener, null).getEntity();
|
||||
}
|
||||
private HttpEntity getEntityForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, ProgressListener progressListener) throws Exception {
|
||||
return getResponseForURL(context, url, requestParams, parameterNames,
|
||||
parameterValues, null, progressListener, null).getEntity();
|
||||
}
|
||||
|
||||
private HttpResponse getResponseForURL(Context context, String url, HttpParams requestParams,
|
||||
List<String> parameterNames, List<Object> parameterValues,
|
||||
List<Header> headers, ProgressListener progressListener, CancellableTask task) throws Exception {
|
||||
Log.d(TAG, "Connections in pool: " + connManager.getConnectionsInPool());
|
||||
private HttpResponse getResponseForURL(Context context, String url, HttpParams requestParams, List<String> parameterNames, List<Object> parameterValues, List<Header> headers, ProgressListener progressListener, CancellableTask task) throws Exception {
|
||||
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
|
||||
// received intact. Remember, HTTP POST requests are converted to GET requests during HTTP redirects, thus
|
||||
// loosing its entity.
|
||||
if (parameterNames != null && parameterNames.size() < 10) {
|
||||
StringBuilder builder = new StringBuilder(url);
|
||||
for (int i = 0; i < parameterNames.size(); i++) {
|
||||
builder.append("&").append(parameterNames.get(i)).append("=");
|
||||
builder.append(URLEncoder.encode(String.valueOf(parameterValues.get(i)), "UTF-8"));
|
||||
}
|
||||
url = builder.toString();
|
||||
parameterNames = null;
|
||||
parameterValues = null;
|
||||
}
|
||||
// If not too many parameters, extract them to the URL rather than
|
||||
// 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.
|
||||
if (parameterNames != null && parameterNames.size() < 10) {
|
||||
StringBuilder builder = new StringBuilder(url);
|
||||
for (int i = 0; i < parameterNames.size(); i++) {
|
||||
builder.append("&").append(parameterNames.get(i)).append("=");
|
||||
builder.append(URLEncoder.encode(String.valueOf(parameterValues.get(i)), "UTF-8"));
|
||||
}
|
||||
url = builder.toString();
|
||||
parameterNames = null;
|
||||
parameterValues = null;
|
||||
}
|
||||
|
||||
String rewrittenUrl = rewriteUrlWithRedirect(context, url);
|
||||
return executeWithRetry(context, rewrittenUrl, url, requestParams, parameterNames, parameterValues, headers, progressListener, task);
|
||||
}
|
||||
String rewrittenUrl = rewriteUrlWithRedirect(context, url);
|
||||
return executeWithRetry(context, rewrittenUrl, url, requestParams, parameterNames, parameterValues, headers, progressListener, task);
|
||||
}
|
||||
|
||||
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 {
|
||||
Log.i(TAG, "Using URL " + url);
|
||||
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 {
|
||||
Log.i(TAG, "Using URL " + url);
|
||||
|
||||
final AtomicReference<Boolean> cancelled = new AtomicReference<Boolean>(false);
|
||||
int attempts = 0;
|
||||
while (true) {
|
||||
attempts++;
|
||||
HttpContext httpContext = new BasicHttpContext();
|
||||
final HttpPost request = new HttpPost(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);
|
||||
|
||||
if (task != null) {
|
||||
// Attempt to abort the HTTP request if the task is cancelled.
|
||||
task.setOnCancelListener(new CancellableTask.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel() {
|
||||
cancelled.set(true);
|
||||
request.abort();
|
||||
}
|
||||
});
|
||||
}
|
||||
final AtomicReference<Boolean> cancelled = new AtomicReference<Boolean>(false);
|
||||
int attempts = 0;
|
||||
while (true) {
|
||||
attempts++;
|
||||
HttpContext httpContext = new BasicHttpContext();
|
||||
final HttpPost request = new HttpPost(url);
|
||||
|
||||
if (parameterNames != null) {
|
||||
List<NameValuePair> params = new ArrayList<NameValuePair>();
|
||||
for (int i = 0; i < parameterNames.size(); i++) {
|
||||
params.add(new BasicNameValuePair(parameterNames.get(i), String.valueOf(parameterValues.get(i))));
|
||||
}
|
||||
request.setEntity(new UrlEncodedFormEntity(params, Constants.UTF_8));
|
||||
}
|
||||
if (task != null) {
|
||||
// Attempt to abort the HTTP request if the task is cancelled.
|
||||
task.setOnCancelListener(new CancellableTask.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel() {
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
cancelled.set(true);
|
||||
request.abort();
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Failed to stop http task");
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (requestParams != null) {
|
||||
request.setParams(requestParams);
|
||||
Log.d(TAG, "Socket read timeout: " + HttpConnectionParams.getSoTimeout(requestParams) + " ms.");
|
||||
}
|
||||
if (parameterNames != null) {
|
||||
List<NameValuePair> params = new ArrayList<NameValuePair>();
|
||||
for (int i = 0; i < parameterNames.size(); i++) {
|
||||
params.add(new BasicNameValuePair(parameterNames.get(i), String.valueOf(parameterValues.get(i))));
|
||||
}
|
||||
request.setEntity(new UrlEncodedFormEntity(params, Constants.UTF_8));
|
||||
}
|
||||
|
||||
if (headers != null) {
|
||||
for (Header header : headers) {
|
||||
request.addHeader(header);
|
||||
}
|
||||
}
|
||||
if (requestParams != null) {
|
||||
request.setParams(requestParams);
|
||||
Log.d(TAG, "Socket read timeout: " + HttpConnectionParams.getSoTimeout(requestParams) + " ms.");
|
||||
}
|
||||
|
||||
// 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);
|
||||
String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
|
||||
String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
|
||||
httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),
|
||||
new UsernamePasswordCredentials(username, password));
|
||||
if (headers != null) {
|
||||
for (Header header : headers) {
|
||||
request.addHeader(header);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
httpClient.getParams().setParameter("http.socket.timeout", Util.getNetworkTimeout(context));
|
||||
|
||||
HttpResponse response = httpClient.execute(request, httpContext);
|
||||
detectRedirect(originalUrl, context, httpContext);
|
||||
return response;
|
||||
} catch (IOException x) {
|
||||
request.abort();
|
||||
if (attempts >= HTTP_REQUEST_MAX_ATTEMPTS || cancelled.get()) {
|
||||
throw x;
|
||||
}
|
||||
if (progressListener != null) {
|
||||
String msg = context.getResources().getString(R.string.music_service_retry, attempts, HTTP_REQUEST_MAX_ATTEMPTS - 1);
|
||||
progressListener.updateProgress(msg);
|
||||
}
|
||||
Log.w(TAG, "Got IOException (" + attempts + "), will retry", x);
|
||||
increaseTimeouts(requestParams);
|
||||
Util.sleepQuietly(2000L);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set credentials to get through apache proxies that require authentication.
|
||||
int instance = prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1);
|
||||
String username = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
|
||||
String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + instance, null);
|
||||
httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),
|
||||
new UsernamePasswordCredentials(username, password));
|
||||
|
||||
try {
|
||||
HttpResponse response = httpClient.execute(request, httpContext);
|
||||
detectRedirect(originalUrl, context, httpContext);
|
||||
return response;
|
||||
} catch (IOException x) {
|
||||
request.abort();
|
||||
if (attempts >= HTTP_REQUEST_MAX_ATTEMPTS || cancelled.get()) {
|
||||
throw x;
|
||||
}
|
||||
if (progressListener != null) {
|
||||
String msg = context.getResources().getString(R.string.music_service_retry, attempts, HTTP_REQUEST_MAX_ATTEMPTS - 1);
|
||||
progressListener.updateProgress(msg);
|
||||
}
|
||||
Log.w(TAG, "Got IOException (" + attempts + "), will retry", x);
|
||||
increaseTimeouts(requestParams);
|
||||
Util.sleepQuietly(2000L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void increaseTimeouts(HttpParams requestParams) {
|
||||
if (requestParams != null) {
|
||||
|
@ -758,7 +903,7 @@ public class RESTMusicService implements MusicService {
|
|||
private void detectRedirect(String originalUrl, Context context, HttpContext httpContext) {
|
||||
HttpUriRequest request = (HttpUriRequest) httpContext.getAttribute(ExecutionContext.HTTP_REQUEST);
|
||||
HttpHost host = (HttpHost) httpContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
|
||||
|
||||
|
||||
// Sometimes the request doesn't contain the "http://host" part
|
||||
String redirectedUrl;
|
||||
if (request.getURI().getScheme() == null) {
|
||||
|
@ -766,7 +911,7 @@ public class RESTMusicService implements MusicService {
|
|||
} else {
|
||||
redirectedUrl = request.getURI().toString();
|
||||
}
|
||||
|
||||
|
||||
redirectFrom = originalUrl.substring(0, originalUrl.indexOf("/rest/"));
|
||||
redirectTo = redirectedUrl.substring(0, redirectedUrl.indexOf("/rest/"));
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ public class AlbumListParser extends MusicDirectoryEntryParser {
|
|||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String name = getElementName();
|
||||
if ("album".equals(name)) {
|
||||
dir.addChild(parseEntry());
|
||||
dir.addChild(parseEntry(""));
|
||||
} else if ("error".equals(name)) {
|
||||
handleError();
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ public class MusicDirectoryEntryParser extends AbstractParser {
|
|||
super(context);
|
||||
}
|
||||
|
||||
protected MusicDirectory.Entry parseEntry() {
|
||||
protected MusicDirectory.Entry parseEntry(String artist) {
|
||||
MusicDirectory.Entry entry = new MusicDirectory.Entry();
|
||||
entry.setId(get("id"));
|
||||
entry.setParent(get("parent"));
|
||||
|
@ -55,7 +55,9 @@ public class MusicDirectoryEntryParser extends AbstractParser {
|
|||
entry.setPath(get("path"));
|
||||
entry.setVideo(getBoolean("isVideo"));
|
||||
entry.setDiscNumber(getInteger("discNumber"));
|
||||
}
|
||||
} else if(!"".equals(artist)) {
|
||||
entry.setPath(artist + "/" + entry.getTitle());
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import com.thejoshwa.ultrasonic.androidapp.R;
|
|||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||
import com.thejoshwa.ultrasonic.androidapp.util.ProgressListener;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
|
@ -38,7 +37,7 @@ public class MusicDirectoryParser extends MusicDirectoryEntryParser {
|
|||
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();
|
||||
updateProgress(progressListener, R.string.parser_reading);
|
||||
|
@ -51,7 +50,7 @@ public class MusicDirectoryParser extends MusicDirectoryEntryParser {
|
|||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String name = getElementName();
|
||||
if ("child".equals(name)) {
|
||||
dir.addChild(parseEntry());
|
||||
dir.addChild(parseEntry(artist));
|
||||
} else if ("directory".equals(name)) {
|
||||
dir.setName(get("name"));
|
||||
} else if ("error".equals(name)) {
|
||||
|
|
|
@ -46,7 +46,7 @@ public class PlaylistParser extends MusicDirectoryEntryParser {
|
|||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String name = getElementName();
|
||||
if ("entry".equals(name)) {
|
||||
dir.addChild(parseEntry());
|
||||
dir.addChild(parseEntry(""));
|
||||
} else if ("error".equals(name)) {
|
||||
handleError();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ import android.content.Context;
|
|||
import com.thejoshwa.ultrasonic.androidapp.R;
|
||||
import com.thejoshwa.ultrasonic.androidapp.domain.Playlist;
|
||||
import com.thejoshwa.ultrasonic.androidapp.util.ProgressListener;
|
||||
import com.thejoshwa.ultrasonic.androidapp.view.PlaylistAdapter;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
import java.io.Reader;
|
||||
|
@ -51,7 +53,12 @@ public class PlaylistsParser extends AbstractParser {
|
|||
if ("playlist".equals(tag)) {
|
||||
String id = get("id");
|
||||
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)) {
|
||||
handleError();
|
||||
}
|
||||
|
@ -61,7 +68,7 @@ public class PlaylistsParser extends AbstractParser {
|
|||
validate();
|
||||
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) {
|
||||
String name = getElementName();
|
||||
if ("song".equals(name)) {
|
||||
dir.addChild(parseEntry());
|
||||
dir.addChild(parseEntry(""));
|
||||
} else if ("error".equals(name)) {
|
||||
handleError();
|
||||
}
|
||||
|
|
|
@ -57,9 +57,9 @@ public class SearchResult2Parser extends MusicDirectoryEntryParser {
|
|||
artist.setName(get("name"));
|
||||
artists.add(artist);
|
||||
} else if ("album".equals(name)) {
|
||||
albums.add(parseEntry());
|
||||
albums.add(parseEntry(""));
|
||||
} else if ("song".equals(name)) {
|
||||
songs.add(parseEntry());
|
||||
songs.add(parseEntry(""));
|
||||
} else if ("error".equals(name)) {
|
||||
handleError();
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ public class SearchResultParser extends MusicDirectoryEntryParser {
|
|||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String name = getElementName();
|
||||
if ("match".equals(name)) {
|
||||
songs.add(parseEntry());
|
||||
songs.add(parseEntry(""));
|
||||
} else if ("error".equals(name)) {
|
||||
handleError();
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
package com.thejoshwa.ultrasonic.androidapp.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.StatFs;
|
||||
|
||||
import com.thejoshwa.ultrasonic.androidapp.domain.Playlist;
|
||||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
||||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadService;
|
||||
|
||||
|
@ -24,7 +25,7 @@ import com.thejoshwa.ultrasonic.androidapp.service.DownloadService;
|
|||
public class CacheCleaner {
|
||||
|
||||
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 DownloadService downloadService;
|
||||
|
@ -35,35 +36,17 @@ public class CacheCleaner {
|
|||
}
|
||||
|
||||
public void clean() {
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
Log.i(TAG, "Starting cache cleaning.");
|
||||
|
||||
if (downloadService == null) {
|
||||
Log.e(TAG, "DownloadService not set. Aborting cache cleaning.");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
deleteEmptyDirs(dirs, undeletable);
|
||||
Log.i(TAG, "Completed cache cleaning.");
|
||||
|
||||
} catch (RuntimeException x) {
|
||||
Log.e(TAG, "Error in cache cleaning.", x);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
new BackgroundCleanup().execute();
|
||||
}
|
||||
|
||||
public void cleanSpace() {
|
||||
new BackgroundSpaceCleanup().execute();
|
||||
}
|
||||
|
||||
public void cleanPlaylists(List<Playlist> playlists) {
|
||||
new BackgroundPlaylistsCleanup().execute(playlists);
|
||||
}
|
||||
|
||||
private void deleteEmptyDirs(List<File> dirs, Set<File> undeletable) {
|
||||
for (File dir : dirs) {
|
||||
if (undeletable.contains(dir)) {
|
||||
|
@ -71,59 +54,62 @@ public class CacheCleaner {
|
|||
}
|
||||
|
||||
File[] children = dir.listFiles();
|
||||
|
||||
// No songs left in the folder
|
||||
if(children.length == 1 && children[0].getPath().equals(FileUtil.getAlbumArtFile(dir).getPath())) {
|
||||
Util.delete(FileUtil.getAlbumArtFile(dir));
|
||||
children = dir.listFiles();
|
||||
}
|
||||
|
||||
// Delete empty directory and associated album artwork.
|
||||
if (children != null && children.length == 0) {
|
||||
// Delete empty directory
|
||||
if (children.length == 0) {
|
||||
Util.delete(dir);
|
||||
Util.delete(FileUtil.getAlbumArtFile(dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long getMinimumDelete(List<File> files) {
|
||||
if(files.size() == 0) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
long cacheSizeBytes = Util.getCacheSizeMB(context) * 1024L * 1024L;
|
||||
|
||||
long bytesUsedBySubsonic = 0L;
|
||||
for (File file : files) {
|
||||
bytesUsedBySubsonic += file.length();
|
||||
}
|
||||
|
||||
// Ensure that file system is not more than 95% full.
|
||||
StatFs stat = new StatFs(files.get(0).getPath());
|
||||
long bytesTotalFs = (long) stat.getBlockCount() * (long) stat.getBlockSize();
|
||||
long bytesAvailableFs = (long) stat.getAvailableBlocks() * (long) stat.getBlockSize();
|
||||
long bytesUsedFs = bytesTotalFs - bytesAvailableFs;
|
||||
long minFsAvailability = bytesTotalFs - MIN_FREE_SPACE;
|
||||
|
||||
private void deleteFiles(List<File> files, Set<File> undeletable) {
|
||||
long bytesToDeleteCacheLimit = Math.max(bytesUsedBySubsonic - cacheSizeBytes, 0L);
|
||||
long bytesToDeleteFsLimit = Math.max(bytesUsedFs - minFsAvailability, 0L);
|
||||
long bytesToDelete = Math.max(bytesToDeleteCacheLimit, bytesToDeleteFsLimit);
|
||||
|
||||
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 size before : " + Util.formatBytes(bytesUsedBySubsonic));
|
||||
Log.i(TAG, "Minimum to delete : " + Util.formatBytes(bytesToDelete));
|
||||
|
||||
return bytesToDelete;
|
||||
}
|
||||
|
||||
private void deleteFiles(List<File> files, Set<File> undeletable, long bytesToDelete, boolean deletePartials) {
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
long cacheSizeBytes = Util.getCacheSizeMB(context) * 1024L * 1024L;
|
||||
|
||||
long bytesUsedByUltraSonic = 0L;
|
||||
for (File file : files) {
|
||||
bytesUsedByUltraSonic += file.length();
|
||||
}
|
||||
|
||||
long bytesToDelete = 0;
|
||||
|
||||
// Ensure that file system is not more than 95% full.
|
||||
try
|
||||
{
|
||||
StatFs stat = new StatFs(files.get(0).getPath());
|
||||
long bytesTotalFs = (long) stat.getBlockCount() * (long) stat.getBlockSize();
|
||||
long bytesAvailableFs = (long) stat.getAvailableBlocks() * (long) stat.getBlockSize();
|
||||
long bytesUsedFs = bytesTotalFs - bytesAvailableFs;
|
||||
long minFsAvailability = Math.round(MAX_FILE_SYSTEM_USAGE * (double) bytesTotalFs);
|
||||
|
||||
long bytesToDeleteCacheLimit = Math.max(bytesUsedByUltraSonic - cacheSizeBytes, 0L);
|
||||
long bytesToDeleteFsLimit = Math.max(bytesUsedFs - minFsAvailability, 0L);
|
||||
bytesToDelete = Math.max(bytesToDeleteCacheLimit, bytesToDeleteFsLimit);
|
||||
|
||||
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 size before : " + Util.formatBytes(bytesUsedByUltraSonic));
|
||||
Log.i(TAG, "Minimum to delete : " + Util.formatBytes(bytesToDelete));
|
||||
} catch (Exception x) {
|
||||
//
|
||||
}
|
||||
|
||||
long bytesDeleted = 0L;
|
||||
for (File file : files) {
|
||||
if (file.getName().equals(Constants.ALBUM_ART_FILE)) {
|
||||
// Move artwork to new folder.
|
||||
file.renameTo(FileUtil.getAlbumArtFile(file.getParentFile()));
|
||||
if(!deletePartials && bytesDeleted > bytesToDelete) break;
|
||||
|
||||
} else if (bytesToDelete > bytesDeleted || file.getName().endsWith(".partial") || file.getName().contains(".partial.")) {
|
||||
if (!undeletable.contains(file)) {
|
||||
if (bytesToDelete > bytesDeleted || (deletePartials && (file.getName().endsWith(".partial") || file.getName().contains(".partial.")))) {
|
||||
if (!undeletable.contains(file) && !file.getName().equals(Constants.ALBUM_ART_FILE)) {
|
||||
long size = file.length();
|
||||
if (Util.delete(file)) {
|
||||
bytesDeleted += size;
|
||||
|
@ -133,15 +119,13 @@ public class CacheCleaner {
|
|||
}
|
||||
|
||||
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) {
|
||||
if (file.isFile()) {
|
||||
String name = file.getName();
|
||||
boolean isCacheFile = name.endsWith(".partial") || name.contains(".partial.") || name.endsWith(".complete") || name.contains(".complete.");
|
||||
boolean isAlbumArtFile = name.equals(Constants.ALBUM_ART_FILE);
|
||||
if (isCacheFile || isAlbumArtFile) {
|
||||
if (isCacheFile) {
|
||||
files.add(file);
|
||||
}
|
||||
} else {
|
||||
|
@ -179,4 +163,79 @@ public class CacheCleaner {
|
|||
undeletable.add(FileUtil.getMusicDirectory(context));
|
||||
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>();
|
||||
|
||||
public void cancel() {
|
||||
Log.d(TAG, "Cancelling " + CancellableTask.this);
|
||||
Log.i(TAG, "Cancelling " + CancellableTask.this);
|
||||
cancelled.set(true);
|
||||
|
||||
OnCancelListener listener = cancelListener.get();
|
||||
|
@ -69,12 +69,12 @@ public abstract class CancellableTask {
|
|||
@Override
|
||||
public void run() {
|
||||
running.set(true);
|
||||
Log.d(TAG, "Starting thread for " + CancellableTask.this);
|
||||
Log.i(TAG, "Starting thread for " + CancellableTask.this);
|
||||
try {
|
||||
execute();
|
||||
} finally {
|
||||
running.set(false);
|
||||
Log.d(TAG, "Stopping thread for " + CancellableTask.this);
|
||||
Log.i(TAG, "Stopping thread for " + CancellableTask.this);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -84,4 +84,4 @@ public abstract class CancellableTask {
|
|||
public static interface OnCancelListener {
|
||||
void onCancel();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -78,6 +78,8 @@ public final class Constants {
|
|||
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_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_LOCATION = "cacheLocation";
|
||||
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_REPEAT_MODE = "repeatMode";
|
||||
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_NETWORK_TIMEOUT = "networkTimeout";
|
||||
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_USE_STREAM_PROXY = "useStreamProxy";
|
||||
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_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.
|
||||
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.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import com.thejoshwa.ultrasonic.androidapp.domain.Artist;
|
||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||
|
||||
/**
|
||||
|
@ -44,11 +46,32 @@ import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
|||
public class FileUtil {
|
||||
|
||||
private static final String TAG = FileUtil.class.getSimpleName();
|
||||
private static final String[] FILE_SYSTEM_UNSAFE = {"/", "\\", "..", ":", "\"", "?", "*", "<", ">"};
|
||||
private static final String[] FILE_SYSTEM_UNSAFE_DIR = {"\\", "..", ":", "\"", "?", "*", "<", ">"};
|
||||
private static final String[] FILE_SYSTEM_UNSAFE = {"/", "\\", "..", ":", "\"", "?", "*", "<", ">", "|"};
|
||||
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> 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");
|
||||
|
||||
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) {
|
||||
File dir = getAlbumDirectory(context, song);
|
||||
|
||||
|
@ -72,14 +95,30 @@ public class FileUtil {
|
|||
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) {
|
||||
File albumDir = getAlbumDirectory(context, entry);
|
||||
return getAlbumArtFile(albumDir);
|
||||
}
|
||||
|
||||
public static File getAlbumArtFile(File albumDir) {
|
||||
File albumArtDir = getAlbumArtDirectory();
|
||||
return new File(albumArtDir, Util.md5Hex(albumDir.getPath()) + ".jpeg");
|
||||
File albumArtDir = getAlbumArtDirectory();
|
||||
return new File(albumArtDir, Util.md5Hex(albumDir.getPath()) + ".jpeg");
|
||||
}
|
||||
|
||||
public static Bitmap getAlbumArtBitmap(Context context, MusicDirectory.Entry entry, int size) {
|
||||
|
@ -102,6 +141,11 @@ public class FileUtil {
|
|||
|
||||
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() {
|
||||
File albumArtDir = new File(getUltraSonicDirectory(), "artwork");
|
||||
|
@ -110,17 +154,17 @@ public class FileUtil {
|
|||
return albumArtDir;
|
||||
}
|
||||
|
||||
private static File getAlbumDirectory(Context context, MusicDirectory.Entry entry) {
|
||||
public static File getAlbumDirectory(Context context, MusicDirectory.Entry entry) {
|
||||
File dir;
|
||||
if (entry.getPath() != null) {
|
||||
File f = new File(fileSystemSafeDir(entry.getPath()));
|
||||
dir = new File(getMusicDirectory(context).getPath() + "/" + (entry.isDirectory() ? f.getPath() : f.getParent()));
|
||||
} else {
|
||||
String artist = fileSystemSafe(entry.getArtist());
|
||||
String album = fileSystemSafe(entry.getAlbum());
|
||||
if (album == "unnamed") {
|
||||
album = fileSystemSafe(entry.getTitle());
|
||||
}
|
||||
String album = fileSystemSafe(entry.getAlbum());
|
||||
if("unnamed".equals(album)) {
|
||||
album = fileSystemSafe(entry.getTitle());
|
||||
}
|
||||
dir = new File(getMusicDirectory(context).getPath() + "/" + artist + "/" + album);
|
||||
}
|
||||
return dir;
|
||||
|
@ -238,23 +282,38 @@ public class FileUtil {
|
|||
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);
|
||||
Iterator<File> iterator = files.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
File file = iterator.next();
|
||||
if (!file.isDirectory() && !isMusicFile(file)) {
|
||||
if (!file.isDirectory() && !isMediaFile(file)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
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);
|
||||
return MUSIC_FILE_EXTENSIONS.contains(extension) || VIDEO_FILE_EXTENSIONS.contains(extension);
|
||||
}
|
||||
|
||||
public static boolean isMusicFile(File file) {
|
||||
String extension = getExtension(file.getName());
|
||||
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
|
||||
* is not included in the returned extension.
|
||||
|
@ -264,7 +323,7 @@ public class FileUtil {
|
|||
*/
|
||||
public static String getExtension(String name) {
|
||||
int index = name.lastIndexOf('.');
|
||||
return index == -1 ? "" : name.substring(index + 1).toLowerCase(Locale.getDefault());
|
||||
return index == -1 ? "" : name.substring(index + 1).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -278,6 +337,19 @@ public class FileUtil {
|
|||
int index = name.lastIndexOf('.');
|
||||
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) {
|
||||
File file = new File(context.getCacheDir(), fileName);
|
||||
|
@ -304,7 +376,7 @@ public class FileUtil {
|
|||
ObjectInputStream in = null;
|
||||
try {
|
||||
in = new ObjectInputStream(new FileInputStream(file));
|
||||
T result = (T)in.readObject();
|
||||
T result = (T) in.readObject();
|
||||
Log.i(TAG, "Deserialized object from " + file);
|
||||
return result;
|
||||
} catch (Throwable x) {
|
||||
|
|
|
@ -22,6 +22,8 @@ import java.lang.ref.SoftReference;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
/**
|
||||
* @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() {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -44,9 +44,9 @@ public class ShufflePlayBuffer {
|
|||
private final List<MusicDirectory.Entry> buffer = new ArrayList<MusicDirectory.Entry>();
|
||||
private Context context;
|
||||
private int currentServer;
|
||||
|
||||
public ShufflePlayBuffer(Context context) {
|
||||
this.context = context;
|
||||
|
||||
public ShufflePlayBuffer(Context context) {
|
||||
this.context = context;
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
|
@ -99,11 +99,10 @@ public class ShufflePlayBuffer {
|
|||
|
||||
private void clearBufferIfnecessary() {
|
||||
synchronized (buffer) {
|
||||
if (currentServer != Util.getActiveServer(context)) {
|
||||
currentServer = Util.getActiveServer(context);
|
||||
if (currentServer != Util.getActiveServer(context)) {
|
||||
currentServer = Util.getActiveServer(context);
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -18,12 +18,13 @@ import java.net.URLDecoder;
|
|||
import java.net.UnknownHostException;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpRequest;
|
||||
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.DownloadService;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
@ -35,25 +36,21 @@ public class StreamProxy implements Runnable {
|
|||
private boolean isRunning;
|
||||
private ServerSocket socket;
|
||||
private int port;
|
||||
private DownloadFile downloadFile;
|
||||
private DownloadService downloadService;
|
||||
|
||||
public StreamProxy() {
|
||||
public StreamProxy(DownloadService downloadService) {
|
||||
|
||||
// Create listening socket
|
||||
try {
|
||||
socket = new ServerSocket(0, 0, InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }));
|
||||
socket.setSoTimeout(5000);
|
||||
port = socket.getLocalPort();
|
||||
this.downloadService = downloadService;
|
||||
} catch (UnknownHostException e) { // impossible
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "IOException initializing server", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDownloadFile(DownloadFile downloadFile) {
|
||||
this.downloadFile = downloadFile;
|
||||
}
|
||||
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
|
@ -67,52 +64,43 @@ public class StreamProxy implements Runnable {
|
|||
public void stop() {
|
||||
isRunning = false;
|
||||
thread.interrupt();
|
||||
try {
|
||||
thread.join(5000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
isRunning = true;
|
||||
|
||||
while (isRunning) {
|
||||
try {
|
||||
Socket client = socket.accept();
|
||||
|
||||
if (client == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.d(TAG, "client connected");
|
||||
Log.i(TAG, "client connected");
|
||||
|
||||
StreamToMediaPlayerTask task = new StreamToMediaPlayerTask(client);
|
||||
|
||||
if (task.processRequest()) {
|
||||
task.execute();
|
||||
new Thread(task).start();
|
||||
}
|
||||
|
||||
} catch (SocketTimeoutException e) {
|
||||
// Do nothing
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error connecting to client", e);
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Proxy interrupted. Shutting down.");
|
||||
Log.i(TAG, "Proxy interrupted. Shutting down.");
|
||||
}
|
||||
|
||||
private class StreamToMediaPlayerTask extends AsyncTask<String, Void, Integer> {
|
||||
File thisFile;
|
||||
private class StreamToMediaPlayerTask implements Runnable {
|
||||
|
||||
String localPath;
|
||||
Socket client;
|
||||
int cbSkip;
|
||||
|
||||
public StreamToMediaPlayerTask(Socket client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
|
||||
private HttpRequest readRequest() {
|
||||
HttpRequest request = null;
|
||||
InputStream is;
|
||||
|
@ -134,122 +122,121 @@ public class StreamProxy implements Runnable {
|
|||
StringTokenizer st = new StringTokenizer(firstLine);
|
||||
String method = st.nextToken();
|
||||
String uri = st.nextToken();
|
||||
Log.d(TAG, uri);
|
||||
String realUri = uri.substring(1);
|
||||
Log.d(TAG, realUri);
|
||||
Log.i(TAG, realUri);
|
||||
request = new BasicHttpRequest(method, realUri);
|
||||
return request;
|
||||
}
|
||||
|
||||
public boolean processRequest() {
|
||||
HttpRequest request = readRequest();
|
||||
|
||||
if (request == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read HTTP headers
|
||||
Log.i(TAG, "Processing request");
|
||||
|
||||
Log.d(TAG, "Processing request");
|
||||
|
||||
thisFile = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
|
||||
|
||||
if (!thisFile.exists()) {
|
||||
Log.e(TAG, "File " + thisFile.getPath() + " does not exist");
|
||||
try {
|
||||
localPath = URLDecoder.decode(request.getRequestLine().getUri(), Constants.UTF_8);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(TAG, "Unsupported encoding", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Log.i(TAG, "Processing request for file " + localPath);
|
||||
File file = new File(localPath);
|
||||
if (!file.exists()) {
|
||||
Log.e(TAG, "File " + localPath + " does not exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(String... params) {
|
||||
long fileSize = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile().length() : downloadFile.getSong().getSize();
|
||||
public void run() {
|
||||
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
|
||||
String headers = "HTTP/1.1 200 OK\r\n";
|
||||
// Create HTTP header
|
||||
String headers = "HTTP/1.0 200 OK\r\n";
|
||||
headers += "Content-Type: " + "application/octet-stream" + "\r\n";
|
||||
|
||||
headers += "Connection: close\r\n";
|
||||
headers += "\r\n";
|
||||
|
||||
if (fileSize > 0) {
|
||||
headers += "Content-Length: " + fileSize + "\r\n";
|
||||
}
|
||||
|
||||
headers += "Content-Type: " + "application/octet-stream" + "\r\n";
|
||||
headers += "Connection: close\r\n";
|
||||
headers += "\r\n";
|
||||
long cbToSend = fileSize - cbSkip;
|
||||
OutputStream output = null;
|
||||
byte[] buff = new byte[64 * 1024];
|
||||
try {
|
||||
output = new BufferedOutputStream(client.getOutputStream(), 32*1024);
|
||||
output.write(headers.getBytes());
|
||||
|
||||
long cbToSend = fileSize - cbSkip;
|
||||
long totalBytesSent = 0;
|
||||
OutputStream output = null;
|
||||
byte[] buff = new byte[64 * 1024];
|
||||
|
||||
try {
|
||||
output = new BufferedOutputStream(client.getOutputStream(), 32 * 1024);
|
||||
output.write(headers.getBytes());
|
||||
|
||||
// Loop as long as there's stuff to send
|
||||
while (isRunning && cbToSend > 0 && !client.isClosed()) {
|
||||
// See if there's more to send
|
||||
int cbSentThisBatch = 0;
|
||||
if(!downloadFile.isWorkDone()) {
|
||||
// Loop as long as there's stuff to send
|
||||
while (isRunning && !client.isClosed()) {
|
||||
|
||||
FileInputStream input = new FileInputStream(thisFile);
|
||||
input.skip(cbSkip);
|
||||
int cbToSendThisBatch = input.available();
|
||||
// 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);
|
||||
int cbToSendThisBatch = input.available();
|
||||
while (cbToSendThisBatch > 0) {
|
||||
int cbToRead = Math.min(cbToSendThisBatch, buff.length);
|
||||
int cbRead = input.read(buff, 0, cbToRead);
|
||||
if (cbRead == -1) {
|
||||
break;
|
||||
}
|
||||
cbToSendThisBatch -= cbRead;
|
||||
cbToSend -= cbRead;
|
||||
output.write(buff, 0, cbRead);
|
||||
output.flush();
|
||||
cbSkip += cbRead;
|
||||
cbSentThisBatch += cbRead;
|
||||
}
|
||||
input.close();
|
||||
}
|
||||
|
||||
while (cbToSendThisBatch > 0) {
|
||||
int cbToRead = Math.min(cbToSendThisBatch, buff.length);
|
||||
int cbRead = input.read(buff, 0, cbToRead);
|
||||
|
||||
if (cbRead == -1) {
|
||||
// Done regardless of whether or not it thinks it is
|
||||
if(downloadFile.isWorkDone() && cbSkip >= file.length()) {
|
||||
break;
|
||||
}
|
||||
|
||||
cbToSendThisBatch -= cbRead;
|
||||
cbToSend -= cbRead;
|
||||
|
||||
output.write(buff, 0, cbRead);
|
||||
output.flush();
|
||||
|
||||
cbSkip += cbRead;
|
||||
cbSentThisBatch += cbRead;
|
||||
totalBytesSent += cbRead;
|
||||
}
|
||||
|
||||
input.close();
|
||||
|
||||
if (!downloadFile.isDownloading()) {
|
||||
if (downloadFile.isCompleteFileAvailable()) {
|
||||
if (downloadFile.getCompleteFile().length() == totalBytesSent) {
|
||||
Log.d(TAG, "Track is no longer being downloaded, sent " + totalBytesSent + " / " + fileSize);
|
||||
break;
|
||||
}
|
||||
// If we did nothing this batch, block for a second
|
||||
if (cbSentThisBatch == 0) {
|
||||
Log.d(TAG, "Blocking until more data appears (" + cbToSend + ")");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
// If we did nothing this batch, block for a second
|
||||
if (cbSentThisBatch == 0) {
|
||||
Log.d(TAG, "Blocking until more data appears");
|
||||
Thread.sleep(500);
|
||||
}
|
||||
} 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");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception thrown from streaming task:");
|
||||
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
catch (SocketException socketException) {
|
||||
Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly");
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.e(TAG, "Exception thrown from streaming task:");
|
||||
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
try {
|
||||
if (output != null) {
|
||||
output.close();
|
||||
}
|
||||
client.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "IOException while cleaning up streaming task:");
|
||||
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
// Cleanup
|
||||
try {
|
||||
if (output != null) {
|
||||
output.close();
|
||||
}
|
||||
client.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Log.e(TAG, "IOException while cleaning up streaming task:");
|
||||
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,6 +36,8 @@ import android.graphics.drawable.Drawable;
|
|||
import android.media.AudioManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
|
@ -400,6 +402,14 @@ public class Util extends DownloadActivity {
|
|||
delete(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
try {
|
||||
|
@ -421,6 +431,24 @@ public class Util extends DownloadActivity {
|
|||
}
|
||||
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) {
|
||||
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) {
|
||||
// Try to keep correct aspect ratio of the original image, do not force a square
|
||||
double aspectRatio = (double)bitmap.getHeight() / (double)bitmap.getWidth();
|
||||
|
@ -1088,6 +1121,18 @@ public class Util extends DownloadActivity {
|
|||
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) {
|
||||
SharedPreferences prefs = getPreferences(context);
|
||||
return Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT, "15000"));
|
||||
|
@ -1137,4 +1182,14 @@ public class Util extends DownloadActivity {
|
|||
SharedPreferences prefs = getPreferences(context);
|
||||
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
|
||||
*/
|
||||
package com.thejoshwa.ultrasonic.androidapp.util;
|
||||
package com.thejoshwa.ultrasonic.androidapp.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import com.thejoshwa.ultrasonic.androidapp.R;
|
||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||
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}.
|
||||
*
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public class AlbumView extends LinearLayout {
|
||||
public class AlbumView extends UpdateView {
|
||||
|
||||
private static final String TAG = AlbumView.class.getSimpleName();
|
||||
private TextView titleView;
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
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.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
|
||||
*/
|
||||
package com.thejoshwa.ultrasonic.androidapp.util;
|
||||
package com.thejoshwa.ultrasonic.androidapp.view;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -25,6 +25,7 @@ import android.view.ViewGroup;
|
|||
import android.widget.ArrayAdapter;
|
||||
import com.thejoshwa.ultrasonic.androidapp.activity.SubsonicTabActivity;
|
||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||
import com.thejoshwa.ultrasonic.androidapp.util.ImageLoader;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
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.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
|
||||
*/
|
||||
package com.thejoshwa.ultrasonic.androidapp.util;
|
||||
package com.thejoshwa.ultrasonic.androidapp.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import com.thejoshwa.ultrasonic.androidapp.R;
|
||||
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
||||
|
@ -36,32 +34,30 @@ import com.thejoshwa.ultrasonic.androidapp.service.DownloadServiceImpl;
|
|||
import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicService;
|
||||
import com.thejoshwa.ultrasonic.androidapp.service.MusicServiceFactory;
|
||||
import com.thejoshwa.ultrasonic.androidapp.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Used to display songs in a {@code ListView}.
|
||||
*
|
||||
* @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 WeakHashMap<SongView, ?> INSTANCES = new WeakHashMap<SongView, Object>();
|
||||
private static Handler handler;
|
||||
|
||||
private CheckedTextView checkedTextView;
|
||||
private ImageView starImageView;
|
||||
private TextView trackTextView;
|
||||
private TextView discTextView;
|
||||
private TextView titleTextView;
|
||||
private TextView artistTextView;
|
||||
private TextView durationTextView;
|
||||
private TextView statusTextView;
|
||||
private MusicDirectory.Entry song;
|
||||
|
||||
public SongView(Context context) {
|
||||
|
||||
private DownloadService downloadService;
|
||||
|
||||
public SongView(Context context) {
|
||||
super(context);
|
||||
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);
|
||||
durationTextView = (TextView) findViewById(R.id.song_duration);
|
||||
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) {
|
||||
|
@ -157,23 +144,38 @@ public class SongView extends LinearLayout implements Checkable {
|
|||
}
|
||||
});
|
||||
|
||||
updateBackground();
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateBackground() {
|
||||
if (downloadService == null) {
|
||||
|
||||
downloadService = DownloadServiceImpl.getInstance();
|
||||
|
||||
if(downloadService == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
downloadService.forSong(song);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
DownloadService downloadService = DownloadServiceImpl.getInstance();
|
||||
@Override
|
||||
protected void update() {
|
||||
if (downloadService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DownloadFile downloadFile = downloadService.forSong(song);
|
||||
File completeFile = downloadFile.getCompleteFile();
|
||||
downloadFile.getCompleteFile();
|
||||
File partialFile = downloadFile.getPartialFile();
|
||||
|
||||
Drawable leftImage = 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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
public void setChecked(boolean 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