firstView() {
21 | return new FirstViewMatcher();
22 | }
23 |
24 | @Override
25 | public boolean matches(Object o) {
26 | if (matchedBefore) {
27 | return false;
28 | } else {
29 | matchedBefore = true;
30 | return true;
31 | }
32 | }
33 |
34 | @Override
35 | public void describeTo(Description description) {
36 | description.appendText(" is the first view that comes along ");
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/net/programmierecke/radiodroid2/HistoryManager.java:
--------------------------------------------------------------------------------
1 | package net.programmierecke.radiodroid2;
2 |
3 | import android.content.Context;
4 |
5 | import net.programmierecke.radiodroid2.station.DataRadioStation;
6 |
7 | public class HistoryManager extends StationSaveManager{
8 | private static final int MAXSIZE = 25;
9 |
10 | @Override
11 | protected String getSaveId(){
12 | return "history";
13 | }
14 |
15 | public HistoryManager(Context ctx) {
16 | super(ctx);
17 | }
18 |
19 | @Override
20 | public void add(DataRadioStation station){
21 | DataRadioStation stationFromHistory = getById(station.StationUuid);
22 | if (stationFromHistory != null) {
23 | listStations.remove(stationFromHistory);
24 | listStations.add(0, stationFromHistory);
25 | Save();
26 | return;
27 | }
28 |
29 | cutList(MAXSIZE - 1);
30 | super.addFront(station);
31 | }
32 |
33 | private void cutList(int count){
34 | if (listStations.size() > count){
35 | listStations = listStations.subList(0,count);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | #
4 |
5 |
9 |
10 |
11 |
12 | ### Please add issues or feature requests on github under issues.
13 | ### Download releases here https://github.com/segler-alex/RadioDroid/releases
14 |
15 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/net/programmierecke/radiodroid2/players/selector/PlayerType.java:
--------------------------------------------------------------------------------
1 | package net.programmierecke.radiodroid2.players.selector;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | public enum PlayerType implements Parcelable {
7 | MPD_SERVER(0),
8 | RADIODROID(1),
9 | EXTERNAL(2),
10 | CAST(3);
11 |
12 | private final int value;
13 |
14 | PlayerType(final int value) {
15 | this.value = value;
16 | }
17 |
18 | public int getValue() {
19 | return value;
20 | }
21 |
22 | @Override
23 | public void writeToParcel(Parcel dest, int flags) {
24 | dest.writeInt(ordinal());
25 | }
26 |
27 | @Override
28 | public int describeContents() {
29 | return 0;
30 | }
31 |
32 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
33 | @Override
34 | public PlayerType createFromParcel(Parcel in) {
35 | return PlayerType.values()[in.readInt()];
36 | }
37 |
38 | @Override
39 | public PlayerType[] newArray(int size) {
40 | return new PlayerType[size];
41 | }
42 | };
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
16 |
17 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_statistic.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
21 |
22 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/page_player_station_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/net/programmierecke/radiodroid2/tests/utils/http/HttpToMockInterceptor.java:
--------------------------------------------------------------------------------
1 | package net.programmierecke.radiodroid2.tests.utils.http;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | import java.io.IOException;
6 | import java.net.InetSocketAddress;
7 |
8 | import okhttp3.HttpUrl;
9 | import okhttp3.Interceptor;
10 | import okhttp3.Request;
11 | import okhttp3.Response;
12 | import okhttp3.mockwebserver.MockWebServer;
13 |
14 | public class HttpToMockInterceptor implements Interceptor {
15 |
16 | private InetSocketAddress address;
17 |
18 | public HttpToMockInterceptor(MockWebServer mockWebServer) {
19 | address = new InetSocketAddress(mockWebServer.getHostName(), mockWebServer.getPort());
20 | }
21 |
22 | @NonNull
23 | @Override
24 | public Response intercept(Chain chain) throws IOException {
25 | Request request = chain.request();
26 |
27 | HttpUrl httpUrl = new HttpUrl.Builder().scheme("http")
28 | .host(address.getHostName())
29 | .port(address.getPort())
30 | .addQueryParameter("url", request.url().toString())
31 | .build();
32 |
33 | request = request.newBuilder()
34 | .url(httpUrl)
35 | .build();
36 |
37 | return chain.proceed(request);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/test/java/net/programmierecke/radiodroid2/UtilsTest.kt:
--------------------------------------------------------------------------------
1 | package net.programmierecke.radiodroid2
2 |
3 | import org.junit.jupiter.api.Assertions.assertFalse
4 | import org.junit.jupiter.api.Test
5 |
6 | internal class UtilsTest {
7 | @Test
8 | fun testUrlIndicatesHlsStream() {
9 | assert(Utils.urlIndicatesHlsStream("http://www.example.org/playlist.m3u8"))
10 | assert(Utils.urlIndicatesHlsStream("http://www.example.org/playlist.m3u8 # cool"))
11 | assert(Utils.urlIndicatesHlsStream("http://www.example.org/playlist.m3u8\n"))
12 | assert(Utils.urlIndicatesHlsStream("https://stream.revma.ihrhls.com/zc5260/hls.m3u8?streamid=5260"))
13 | assert(Utils.urlIndicatesHlsStream("http://www.example.org/playlist.m3u8?bitrate=256&z=43"))
14 | assert(Utils.urlIndicatesHlsStream("http://www.example.org/playlist.m3u8#0100"))
15 | assert(Utils.urlIndicatesHlsStream("http://www.example.org/playlist.m3u8#START"))
16 | assert(Utils.urlIndicatesHlsStream("http://www.example.org/m3u8playlist.m3u8"))
17 |
18 | assertFalse(Utils.urlIndicatesHlsStream("http://www.example.org/no.m3u8.m3u"))
19 | assertFalse(Utils.urlIndicatesHlsStream("http://www.example.org/playlist.m3u85"))
20 | assertFalse(Utils.urlIndicatesHlsStream("http://www.example.org/playlist.m3united"))
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/net/programmierecke/radiodroid2/station/live/metadata/lastfm/data/Album.java:
--------------------------------------------------------------------------------
1 |
2 | package net.programmierecke.radiodroid2.station.live.metadata.lastfm.data;
3 |
4 | import java.util.List;
5 | import com.google.gson.annotations.Expose;
6 | import com.google.gson.annotations.SerializedName;
7 |
8 | public class Album {
9 |
10 | @SerializedName("artist")
11 | @Expose
12 | private String artist;
13 | @SerializedName("title")
14 | @Expose
15 | private String title;
16 | @SerializedName("url")
17 | @Expose
18 | private String url;
19 | @SerializedName("image")
20 | @Expose
21 | private List image = null;
22 |
23 | public String getArtist() {
24 | return artist;
25 | }
26 |
27 | public void setArtist(String artist) {
28 | this.artist = artist;
29 | }
30 |
31 | public String getTitle() {
32 | return title;
33 | }
34 |
35 | public void setTitle(String title) {
36 | this.title = title;
37 | }
38 |
39 | public String getUrl() {
40 | return url;
41 | }
42 |
43 | public void setUrl(String url) {
44 | this.url = url;
45 | }
46 |
47 | public List getImage() {
48 | return image;
49 | }
50 |
51 | public void setImage(List image) {
52 | this.image = image;
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_category.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
26 |
27 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/java/net/programmierecke/radiodroid2/FragmentAbout.java:
--------------------------------------------------------------------------------
1 | package net.programmierecke.radiodroid2;
2 |
3 | import android.content.res.Resources;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.TextView;
9 |
10 | import androidx.annotation.NonNull;
11 | import androidx.annotation.Nullable;
12 | import androidx.fragment.app.Fragment;
13 |
14 | public class FragmentAbout extends Fragment {
15 | @Nullable
16 | @Override
17 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
18 | View view = inflater.inflate(R.layout.layout_about,null);
19 |
20 | TextView aTextVersion = (TextView) view.findViewById(R.id.about_version);
21 | if (aTextVersion != null) {
22 |
23 | String version = BuildConfig.VERSION_NAME;
24 | String gitHash = getString(R.string.GIT_HASH);
25 | String buildDate = getString(R.string.BUILD_DATE);
26 |
27 |
28 | if (!gitHash.isEmpty()) {
29 | version += " (git " + gitHash + ")";
30 | }
31 |
32 | Resources resources = getResources();
33 | aTextVersion.setText(resources.getString(R.string.about_version, version+" "+buildDate));
34 |
35 | }
36 |
37 | return view;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/net/programmierecke/radiodroid2/data/DataStatistics.java:
--------------------------------------------------------------------------------
1 | package net.programmierecke.radiodroid2.data;
2 |
3 | import android.text.TextUtils;
4 |
5 | import org.json.JSONException;
6 | import org.json.JSONObject;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Iterator;
10 | import java.util.List;
11 |
12 | public class DataStatistics {
13 | public String Name = "";
14 | public String Value = "";
15 |
16 | public static DataStatistics[] DecodeJson(String result) {
17 | List aList = new ArrayList();
18 | if (result != null) {
19 | if (TextUtils.isGraphic(result)) {
20 | try {
21 | JSONObject jsonObject = new JSONObject(result);
22 |
23 | Iterator> keys = jsonObject.keys();
24 | while( keys.hasNext() ) {
25 | String key = (String)keys.next();
26 |
27 | DataStatistics aData = new DataStatistics();
28 | aData.Name = key;
29 | aData.Value = jsonObject.getString(key);
30 |
31 | aList.add(aData);
32 | }
33 | } catch (JSONException e) {
34 | e.printStackTrace();
35 | }
36 |
37 | }
38 | }
39 | return aList.toArray(new DataStatistics[0]);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/net/programmierecke/radiodroid2/recording/RunningRecordingInfo.java:
--------------------------------------------------------------------------------
1 | package net.programmierecke.radiodroid2.recording;
2 |
3 | import java.io.FileOutputStream;
4 |
5 | public class RunningRecordingInfo {
6 | private Recordable recordable;
7 | private String title;
8 | private String fileName;
9 | private FileOutputStream outputStream;
10 | private long bytesWritten;
11 |
12 | public Recordable getRecordable() {
13 | return recordable;
14 | }
15 |
16 | protected void setRecordable(Recordable recordable) {
17 | this.recordable = recordable;
18 | }
19 |
20 | public String getTitle() {
21 | return title;
22 | }
23 |
24 | protected void setTitle(String title) {
25 | this.title = title;
26 | }
27 |
28 | public String getFileName() {
29 | return fileName;
30 | }
31 |
32 | protected void setFileName(String fileName) {
33 | this.fileName = fileName;
34 | }
35 |
36 | public FileOutputStream getOutputStream() {
37 | return outputStream;
38 | }
39 |
40 | protected void setOutputStream(FileOutputStream outputStream) {
41 | this.outputStream = outputStream;
42 | }
43 |
44 | public long getBytesWritten() {
45 | return bytesWritten;
46 | }
47 |
48 | protected void setBytesWritten(long bytesWritten) {
49 | this.bytesWritten = bytesWritten;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/net/programmierecke/radiodroid2/players/PlayerWrapper.java:
--------------------------------------------------------------------------------
1 | package net.programmierecke.radiodroid2.players;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import net.programmierecke.radiodroid2.station.live.ShoutcastInfo;
8 | import net.programmierecke.radiodroid2.station.live.StreamLiveInfo;
9 | import net.programmierecke.radiodroid2.recording.Recordable;
10 |
11 | import okhttp3.OkHttpClient;
12 |
13 | public interface PlayerWrapper extends Recordable {
14 | interface PlayListener {
15 | void onStateChanged(PlayState state);
16 |
17 | void onPlayerWarning(final int messageId);
18 |
19 | void onPlayerError(final int messageId);
20 |
21 | void onDataSourceShoutcastInfo(ShoutcastInfo shoutcastInfo, boolean isHls);
22 |
23 | void onDataSourceStreamLiveInfo(StreamLiveInfo liveInfo);
24 | }
25 |
26 | void playRemote(@NonNull OkHttpClient httpClient, @NonNull String streamUrl, @NonNull Context context, boolean isAlarm);
27 |
28 | void pause();
29 |
30 | void stop();
31 |
32 | boolean isPlaying();
33 |
34 | long getBufferedMs();
35 |
36 | int getAudioSessionId();
37 |
38 | long getTotalTransferredBytes();
39 |
40 | long getCurrentPlaybackTransferredBytes();
41 |
42 | boolean isLocal();
43 |
44 | void setVolume(float newVolume);
45 |
46 | void setStateListener(PlayListener listener);
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_station_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/net/programmierecke/radiodroid2/tests/utils/ScrollToRecyclerItemAction.java:
--------------------------------------------------------------------------------
1 | package net.programmierecke.radiodroid2.tests.utils;
2 |
3 | import android.view.View;
4 |
5 | import androidx.recyclerview.widget.RecyclerView;
6 | import androidx.test.espresso.UiController;
7 | import androidx.test.espresso.ViewAction;
8 |
9 | import org.hamcrest.Matcher;
10 |
11 | import static androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
12 |
13 | public class ScrollToRecyclerItemAction implements ViewAction {
14 | private static final int VIEW_DISPLAY_PERCENTAGE = 50;
15 |
16 | private int itemIdx;
17 |
18 | public static ScrollToRecyclerItemAction scrollToRecyclerItem(int itemIdx) {
19 | return new ScrollToRecyclerItemAction(itemIdx);
20 | }
21 |
22 | public ScrollToRecyclerItemAction(int itemIdx) {
23 | this.itemIdx = itemIdx;
24 | }
25 |
26 | @Override
27 | public Matcher getConstraints() {
28 | return isDisplayingAtLeast(VIEW_DISPLAY_PERCENTAGE);
29 | }
30 |
31 | @Override
32 | public String getDescription() {
33 | return String.format("scroll to item %d in recycler", itemIdx);
34 | }
35 |
36 | @Override
37 | public void perform(UiController uiController, View view) {
38 | RecyclerView recyclerView = (RecyclerView) view;
39 | TestUtils.centerItemInRecycler(uiController, recyclerView, itemIdx);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/net/programmierecke/radiodroid2/tests/utils/ViewPagerIdlingResource.java:
--------------------------------------------------------------------------------
1 | package net.programmierecke.radiodroid2.tests.utils;
2 |
3 | import androidx.test.espresso.IdlingResource;
4 | import androidx.viewpager.widget.ViewPager;
5 |
6 | public class ViewPagerIdlingResource implements IdlingResource {
7 | private final String resourceName;
8 |
9 | private boolean isIdle = true;
10 |
11 | private ResourceCallback resourceCallback;
12 |
13 | public ViewPagerIdlingResource(ViewPager viewPager, String name) {
14 | viewPager.addOnPageChangeListener(new ViewPagerListener());
15 | resourceName = name;
16 | }
17 |
18 | @Override
19 | public String getName() {
20 | return resourceName;
21 | }
22 |
23 | @Override
24 | public boolean isIdleNow() {
25 | return isIdle;
26 | }
27 |
28 | @Override
29 | public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
30 | this.resourceCallback = resourceCallback;
31 | }
32 |
33 | private class ViewPagerListener extends ViewPager.SimpleOnPageChangeListener {
34 |
35 | @Override
36 | public void onPageScrollStateChanged(int state) {
37 | isIdle = (state == ViewPager.SCROLL_STATE_IDLE);
38 | if (isIdle && resourceCallback != null) {
39 | resourceCallback.onTransitionToIdle();
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/net/programmierecke/radiodroid2/tests/utils/RecyclerRecyclingMatcher.java:
--------------------------------------------------------------------------------
1 | package net.programmierecke.radiodroid2.tests.utils;
2 |
3 | import android.util.DisplayMetrics;
4 | import android.view.View;
5 |
6 | import androidx.recyclerview.widget.RecyclerView;
7 | import androidx.test.platform.app.InstrumentationRegistry;
8 |
9 | import org.hamcrest.Description;
10 | import org.hamcrest.Matcher;
11 | import org.hamcrest.TypeSafeMatcher;
12 |
13 | public class RecyclerRecyclingMatcher {
14 | private static float MAX_SIZE_K = 1.25f;
15 |
16 | public static Matcher recyclerRecycles() {
17 | return new TypeSafeMatcher(RecyclerView.class) {
18 | @Override
19 | protected boolean matchesSafely(View recyclerView) {
20 | DisplayMetrics displayMetrics = InstrumentationRegistry.getInstrumentation()
21 | .getTargetContext().getResources()
22 | .getDisplayMetrics();
23 |
24 | return !(recyclerView.getMeasuredHeight() > displayMetrics.heightPixels * MAX_SIZE_K) &&
25 | !(recyclerView.getMeasuredWidth() > displayMetrics.widthPixels * MAX_SIZE_K);
26 | }
27 |
28 | @Override
29 | public void describeTo(Description description) {
30 | description.appendText("as a Recycler does recycling of elements");
31 | }
32 | };
33 | }
34 | }
35 |
--------------------------------------------------------------------------------