Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab43ce7794 | |||
| 24a4c93fb5 | |||
| eba119493c | |||
| 87780cddee | |||
| 672774e216 |
@@ -13,10 +13,11 @@ android {
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
signingConfig signingConfigs.debug
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -44,6 +45,9 @@ dependencies {
|
||||
|
||||
// ExoPlayer para reproducción de video
|
||||
implementation 'com.google.android.exoplayer:exoplayer:2.18.7'
|
||||
implementation 'com.google.android.exoplayer:extension-okhttp:2.18.7'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.12.0'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
|
||||
@@ -5,9 +5,16 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:banner="@drawable/banner_streamplayer"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
@@ -28,6 +35,11 @@
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
@@ -43,6 +43,11 @@ public class ChannelAdapter extends RecyclerView.Adapter<ChannelAdapter.ChannelV
|
||||
listener.onChannelClick(channel);
|
||||
}
|
||||
});
|
||||
holder.itemView.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
float scale = hasFocus ? 1.08f : 1f;
|
||||
v.animate().scaleX(scale).scaleY(scale).setDuration(120).start();
|
||||
v.setSelected(hasFocus);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package com.streamplayer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public final class ChannelRepository {
|
||||
|
||||
private static final List<StreamChannel> CHANNELS = Collections.unmodifiableList(Arrays.asList(
|
||||
private static final List<StreamChannel> CHANNELS = createChannels();
|
||||
|
||||
private static List<StreamChannel> createChannels() {
|
||||
List<StreamChannel> channels = new ArrayList<>(Arrays.asList(
|
||||
new StreamChannel("ESPN", "https://streamtpmedia.com/global2.php?stream=espn"),
|
||||
new StreamChannel("ESPN 2", "https://streamtpmedia.com/global2.php?stream=espn2"),
|
||||
new StreamChannel("ESPN 3", "https://streamtpmedia.com/global2.php?stream=espn3"),
|
||||
@@ -73,7 +78,10 @@ public final class ChannelRepository {
|
||||
new StreamChannel("TUDN MX", "https://streamtpmedia.com/global2.php?stream=TUDNMX"),
|
||||
new StreamChannel("FUTV", "https://streamtpmedia.com/global2.php?stream=futv"),
|
||||
new StreamChannel("LaLiga Hypermotion", "https://streamtpmedia.com/global2.php?stream=laligahypermotion")
|
||||
));
|
||||
));
|
||||
channels.sort(Comparator.comparing(StreamChannel::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
return Collections.unmodifiableList(channels);
|
||||
}
|
||||
|
||||
private ChannelRepository() {
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
RecyclerView recyclerView = findViewById(R.id.channel_grid);
|
||||
recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
|
||||
recyclerView.setLayoutManager(new GridLayoutManager(this, getSpanCount()));
|
||||
recyclerView.setHasFixedSize(true);
|
||||
ChannelAdapter adapter = new ChannelAdapter(
|
||||
ChannelRepository.getChannels(),
|
||||
channel -> {
|
||||
@@ -25,5 +26,10 @@ public class MainActivity extends AppCompatActivity {
|
||||
startActivity(intent);
|
||||
});
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.post(recyclerView::requestFocus);
|
||||
}
|
||||
|
||||
private int getSpanCount() {
|
||||
return getResources().getInteger(R.integer.channel_grid_span);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,22 @@ import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.PlaybackException;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
||||
import com.google.android.exoplayer2.ui.PlayerView;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.dnsoverhttps.DnsOverHttps;
|
||||
|
||||
public class PlayerActivity extends AppCompatActivity {
|
||||
|
||||
@@ -32,6 +47,7 @@ public class PlayerActivity extends AppCompatActivity {
|
||||
private String channelName;
|
||||
private String channelUrl;
|
||||
private boolean overlayVisible = true;
|
||||
private OkHttpClient okHttpClient;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -91,7 +107,13 @@ public class PlayerActivity extends AppCompatActivity {
|
||||
private void startPlayback(String streamUrl) {
|
||||
try {
|
||||
releasePlayer();
|
||||
player = new ExoPlayer.Builder(this).build();
|
||||
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this)
|
||||
.setEnableDecoderFallback(true)
|
||||
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON);
|
||||
player = new ExoPlayer.Builder(this, renderersFactory)
|
||||
.setSeekForwardIncrementMs(10_000)
|
||||
.setSeekBackIncrementMs(10_000)
|
||||
.build();
|
||||
playerView.setPlayer(player);
|
||||
|
||||
player.addListener(new Player.Listener() {
|
||||
@@ -106,12 +128,14 @@ public class PlayerActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public void onPlayerError(PlaybackException error) {
|
||||
showError("Error al reproducir: " + error.getMessage());
|
||||
String detail = error.getCause() != null ?
|
||||
error.getCause().getMessage() : "";
|
||||
showError("Error al reproducir: " + error.getMessage() + " " + detail);
|
||||
}
|
||||
});
|
||||
|
||||
MediaItem mediaItem = MediaItem.fromUri(streamUrl);
|
||||
player.setMediaItem(mediaItem);
|
||||
player.setMediaSource(buildMediaSource(mediaItem));
|
||||
player.prepare();
|
||||
player.setPlayWhenReady(true);
|
||||
setOverlayVisible(false);
|
||||
@@ -145,6 +169,55 @@ public class PlayerActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private MediaSource buildMediaSource(MediaItem mediaItem) {
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Referer", channelUrl);
|
||||
headers.put("Origin", "https://streamtpmedia.com");
|
||||
headers.put("Accept", "*/*");
|
||||
headers.put("Connection", "keep-alive");
|
||||
|
||||
String userAgent = Util.getUserAgent(this, "StreamPlayer");
|
||||
|
||||
OkHttpDataSource.Factory factory = new OkHttpDataSource.Factory(provideOkHttpClient())
|
||||
.setUserAgent(userAgent)
|
||||
.setDefaultRequestProperties(headers);
|
||||
return new HlsMediaSource.Factory(factory).createMediaSource(mediaItem);
|
||||
}
|
||||
|
||||
private OkHttpClient provideOkHttpClient() {
|
||||
if (okHttpClient != null) {
|
||||
return okHttpClient;
|
||||
}
|
||||
|
||||
try {
|
||||
OkHttpClient bootstrap = new OkHttpClient.Builder()
|
||||
.connectTimeout(15, TimeUnit.SECONDS)
|
||||
.readTimeout(15, TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(true)
|
||||
.build();
|
||||
|
||||
DnsOverHttps dohDns = new DnsOverHttps.Builder()
|
||||
.client(bootstrap)
|
||||
.url(HttpUrl.get("https://dns.adguard-dns.com/dns-query"))
|
||||
.bootstrapDnsHosts(
|
||||
InetAddress.getByName("94.140.14.14"),
|
||||
InetAddress.getByName("94.140.15.15"))
|
||||
.build();
|
||||
|
||||
okHttpClient = bootstrap.newBuilder()
|
||||
.dns(dohDns)
|
||||
.build();
|
||||
} catch (UnknownHostException e) {
|
||||
okHttpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(15, TimeUnit.SECONDS)
|
||||
.readTimeout(15, TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
return okHttpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
18
app/src/main/res/drawable/banner_streamplayer.xml
Normal file
18
app/src/main/res/drawable/banner_streamplayer.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:endColor="#FF002766"
|
||||
android:startColor="#FF0F4C81" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap
|
||||
android:antialias="true"
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#33212121" />
|
||||
<corners android:radius="12dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#55FFFFFF" />
|
||||
</shape>
|
||||
39
app/src/main/res/drawable/bg_channel_item_selector.xml
Normal file
39
app/src/main/res/drawable/bg_channel_item_selector.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#88003C8F" />
|
||||
<corners android:radius="18dp" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="#FFFFFFFF" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_focused="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#55003C8F" />
|
||||
<corners android:radius="18dp" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="#FFFFFFFF" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#55003C8F" />
|
||||
<corners android:radius="18dp" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="#FFFFFFFF" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#33212121" />
|
||||
<corners android:radius="18dp" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="#33FFFFFF" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
@@ -3,7 +3,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="6dp"
|
||||
android:background="@drawable/bg_channel_item"
|
||||
android:background="@drawable/bg_channel_item_selector"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:defaultFocusHighlightEnabled="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
4
app/src/main/res/values-sw720dp/integers.xml
Normal file
4
app/src/main/res/values-sw720dp/integers.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="channel_grid_span">5</integer>
|
||||
</resources>
|
||||
15
app/src/main/res/values/arrays.xml
Normal file
15
app/src/main/res/values/arrays.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="channel_entries">
|
||||
<item>Azteca Deportes</item>
|
||||
<item>Canal 5 MX</item>
|
||||
<item>Caliente TV MX</item>
|
||||
<item>DAZN 1</item>
|
||||
<item>DAZN 2</item>
|
||||
<item>DAZN LaLiga</item>
|
||||
<item>DSports</item>
|
||||
<item>DSports 2</item>
|
||||
<item>DSports Plus</item>
|
||||
<item>ESPN</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
4
app/src/main/res/values/integers.xml
Normal file
4
app/src/main/res/values/integers.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="channel_grid_span">3</integer>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user