Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96c4c360ee | |||
| ab43ce7794 | |||
| 24a4c93fb5 | |||
| eba119493c | |||
| 87780cddee | |||
| 672774e216 | |||
| e6b4d0825b |
@@ -15,6 +15,7 @@ android {
|
|||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,9 +41,13 @@ dependencies {
|
|||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||||
|
|
||||||
// ExoPlayer para reproducción de video
|
// ExoPlayer para reproducción de video
|
||||||
implementation 'com.google.android.exoplayer:exoplayer:2.18.7'
|
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'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
|
|||||||
Binary file not shown.
@@ -5,9 +5,16 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.CHANGE_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
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
android:banner="@drawable/banner_streamplayer"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
@@ -15,14 +22,24 @@
|
|||||||
android:theme="@style/Theme.StreamPlayer"
|
android:theme="@style/Theme.StreamPlayer"
|
||||||
android:usesCleartextTraffic="true">
|
android:usesCleartextTraffic="true">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".PlayerActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:screenOrientation="landscape" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true">
|
||||||
android:screenOrientation="landscape">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
68
app/src/main/java/com/streamplayer/ChannelAdapter.java
Normal file
68
app/src/main/java/com/streamplayer/ChannelAdapter.java
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package com.streamplayer;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ChannelAdapter extends RecyclerView.Adapter<ChannelAdapter.ChannelViewHolder> {
|
||||||
|
|
||||||
|
public interface OnChannelClickListener {
|
||||||
|
void onChannelClick(StreamChannel channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<StreamChannel> channels;
|
||||||
|
private final OnChannelClickListener listener;
|
||||||
|
|
||||||
|
public ChannelAdapter(List<StreamChannel> channels, OnChannelClickListener listener) {
|
||||||
|
this.channels = channels;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ChannelViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_channel, parent, false);
|
||||||
|
return new ChannelViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ChannelViewHolder holder, int position) {
|
||||||
|
StreamChannel channel = channels.get(position);
|
||||||
|
holder.name.setText(channel.getName());
|
||||||
|
holder.icon.setImageResource(R.drawable.ic_channel_default);
|
||||||
|
holder.itemView.setOnClickListener(v -> {
|
||||||
|
if (listener != null) {
|
||||||
|
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
|
||||||
|
public int getItemCount() {
|
||||||
|
return channels.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ChannelViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
final ImageView icon;
|
||||||
|
final TextView name;
|
||||||
|
|
||||||
|
ChannelViewHolder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
icon = itemView.findViewById(R.id.channel_icon);
|
||||||
|
name = itemView.findViewById(R.id.channel_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
92
app/src/main/java/com/streamplayer/ChannelRepository.java
Normal file
92
app/src/main/java/com/streamplayer/ChannelRepository.java
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
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 = 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"),
|
||||||
|
new StreamChannel("ESPN 4", "https://streamtpmedia.com/global2.php?stream=espn4"),
|
||||||
|
new StreamChannel("ESPN 3 MX", "https://streamtpmedia.com/global2.php?stream=espn3mx"),
|
||||||
|
new StreamChannel("ESPN 5", "https://streamtpmedia.com/global2.php?stream=espn5"),
|
||||||
|
new StreamChannel("Fox Sports 3 MX", "https://streamtpmedia.com/global2.php?stream=foxsports3mx"),
|
||||||
|
new StreamChannel("ESPN 6", "https://streamtpmedia.com/global2.php?stream=espn6"),
|
||||||
|
new StreamChannel("Fox Sports MX", "https://streamtpmedia.com/global2.php?stream=foxsportsmx"),
|
||||||
|
new StreamChannel("ESPN 7", "https://streamtpmedia.com/global2.php?stream=espn7"),
|
||||||
|
new StreamChannel("Azteca Deportes", "https://streamtpmedia.com/global2.php?stream=azteca_deportes"),
|
||||||
|
new StreamChannel("Win Plus", "https://streamtpmedia.com/global2.php?stream=winplus"),
|
||||||
|
new StreamChannel("DAZN 1", "https://streamtpmedia.com/global2.php?stream=dazn1"),
|
||||||
|
new StreamChannel("Win Plus 2", "https://streamtpmedia.com/global2.php?stream=winplus2"),
|
||||||
|
new StreamChannel("DAZN 2", "https://streamtpmedia.com/global2.php?stream=dazn2"),
|
||||||
|
new StreamChannel("Win Sports", "https://streamtpmedia.com/global2.php?stream=winsports"),
|
||||||
|
new StreamChannel("DAZN LaLiga", "https://streamtpmedia.com/global2.php?stream=dazn_laliga"),
|
||||||
|
new StreamChannel("Win Plus Online 1", "https://streamtpmedia.com/global2.php?stream=winplusonline1"),
|
||||||
|
new StreamChannel("Caracol TV", "https://streamtpmedia.com/global2.php?stream=caracoltv"),
|
||||||
|
new StreamChannel("Fox 1 AR", "https://streamtpmedia.com/global2.php?stream=fox1ar"),
|
||||||
|
new StreamChannel("Fox 2 USA", "https://streamtpmedia.com/global2.php?stream=fox_2_usa"),
|
||||||
|
new StreamChannel("Fox 2 AR", "https://streamtpmedia.com/global2.php?stream=fox2ar"),
|
||||||
|
new StreamChannel("TNT 1 GB", "https://streamtpmedia.com/global2.php?stream=tnt_1_gb"),
|
||||||
|
new StreamChannel("TNT 2 GB", "https://streamtpmedia.com/global2.php?stream=tnt_2_gb"),
|
||||||
|
new StreamChannel("Fox 3 AR", "https://streamtpmedia.com/global2.php?stream=fox3ar"),
|
||||||
|
new StreamChannel("Universo USA", "https://streamtpmedia.com/global2.php?stream=universo_usa"),
|
||||||
|
new StreamChannel("DSports", "https://streamtpmedia.com/global2.php?stream=dsports"),
|
||||||
|
new StreamChannel("Univision USA", "https://streamtpmedia.com/global2.php?stream=univision_usa"),
|
||||||
|
new StreamChannel("DSports 2", "https://streamtpmedia.com/global2.php?stream=dsports2"),
|
||||||
|
new StreamChannel("Fox Deportes USA", "https://streamtpmedia.com/global2.php?stream=fox_deportes_usa"),
|
||||||
|
new StreamChannel("DSports Plus", "https://streamtpmedia.com/global2.php?stream=dsportsplus"),
|
||||||
|
new StreamChannel("Fox Sports 2 MX", "https://streamtpmedia.com/global2.php?stream=foxsports2mx"),
|
||||||
|
new StreamChannel("TNT Sports Chile", "https://streamtpmedia.com/global2.php?stream=tntsportschile"),
|
||||||
|
new StreamChannel("Fox Sports Premium", "https://streamtpmedia.com/global2.php?stream=foxsportspremium"),
|
||||||
|
new StreamChannel("TNT Sports", "https://streamtpmedia.com/global2.php?stream=tntsports"),
|
||||||
|
new StreamChannel("ESPN MX", "https://streamtpmedia.com/global2.php?stream=espnmx"),
|
||||||
|
new StreamChannel("ESPN Premium", "https://streamtpmedia.com/global2.php?stream=espnpremium"),
|
||||||
|
new StreamChannel("ESPN 2 MX", "https://streamtpmedia.com/global2.php?stream=espn2mx"),
|
||||||
|
new StreamChannel("TyC Sports", "https://streamtpmedia.com/global2.php?stream=tycsports"),
|
||||||
|
new StreamChannel("TUDN USA", "https://streamtpmedia.com/global2.php?stream=tudn_usa"),
|
||||||
|
new StreamChannel("Telefe", "https://streamtpmedia.com/global2.php?stream=telefe"),
|
||||||
|
new StreamChannel("TNT 3 GB", "https://streamtpmedia.com/global2.php?stream=tnt_3_gb"),
|
||||||
|
new StreamChannel("TV Pública", "https://streamtpmedia.com/global2.php?stream=tv_publica"),
|
||||||
|
new StreamChannel("Fox 1 USA", "https://streamtpmedia.com/global2.php?stream=fox_1_usa"),
|
||||||
|
new StreamChannel("Liga 1 Max", "https://streamtpmedia.com/global2.php?stream=liga1max"),
|
||||||
|
new StreamChannel("Gol TV", "https://streamtpmedia.com/global2.php?stream=goltv"),
|
||||||
|
new StreamChannel("VTV Plus", "https://streamtpmedia.com/global2.php?stream=vtvplus"),
|
||||||
|
new StreamChannel("ESPN Deportes", "https://streamtpmedia.com/global2.php?stream=espndeportes"),
|
||||||
|
new StreamChannel("Gol Perú", "https://streamtpmedia.com/global2.php?stream=golperu"),
|
||||||
|
new StreamChannel("TNT 4 GB", "https://streamtpmedia.com/global2.php?stream=tnt_4_gb"),
|
||||||
|
new StreamChannel("SportTV BR 1", "https://streamtpmedia.com/global2.php?stream=sporttvbr1"),
|
||||||
|
new StreamChannel("SportTV BR 2", "https://streamtpmedia.com/global2.php?stream=sporttvbr2"),
|
||||||
|
new StreamChannel("SportTV BR 3", "https://streamtpmedia.com/global2.php?stream=sporttvbr3"),
|
||||||
|
new StreamChannel("Premiere 1", "https://streamtpmedia.com/global2.php?stream=premiere1"),
|
||||||
|
new StreamChannel("Premiere 2", "https://streamtpmedia.com/global2.php?stream=premiere2"),
|
||||||
|
new StreamChannel("Premiere 3", "https://streamtpmedia.com/global2.php?stream=premiere3"),
|
||||||
|
new StreamChannel("ESPN NL 1", "https://streamtpmedia.com/global2.php?stream=espn_nl1"),
|
||||||
|
new StreamChannel("ESPN NL 2", "https://streamtpmedia.com/global2.php?stream=espn_nl2"),
|
||||||
|
new StreamChannel("ESPN NL 3", "https://streamtpmedia.com/global2.php?stream=espn_nl3"),
|
||||||
|
new StreamChannel("Caliente TV MX", "https://streamtpmedia.com/global2.php?stream=calientetvmx"),
|
||||||
|
new StreamChannel("USA Network", "https://streamtpmedia.com/global2.php?stream=usa_network"),
|
||||||
|
new StreamChannel("TyC Internacional", "https://streamtpmedia.com/global2.php?stream=tycinternacional"),
|
||||||
|
new StreamChannel("Canal 5 MX", "https://streamtpmedia.com/global2.php?stream=canal5mx"),
|
||||||
|
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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<StreamChannel> getChannels() {
|
||||||
|
return CHANNELS;
|
||||||
|
}
|
||||||
|
}
|
||||||
97
app/src/main/java/com/streamplayer/EventAdapter.java
Normal file
97
app/src/main/java/com/streamplayer/EventAdapter.java
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package com.streamplayer;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class EventAdapter extends RecyclerView.Adapter<EventAdapter.EventViewHolder> {
|
||||||
|
|
||||||
|
public interface OnEventClickListener {
|
||||||
|
void onEventClick(EventItem event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<EventItem> events = new ArrayList<>();
|
||||||
|
private final OnEventClickListener listener;
|
||||||
|
|
||||||
|
public EventAdapter(OnEventClickListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void submitList(List<EventItem> newEvents) {
|
||||||
|
events.clear();
|
||||||
|
events.addAll(newEvents);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public EventViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_event, parent, false);
|
||||||
|
return new EventViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull EventViewHolder holder, int position) {
|
||||||
|
EventItem event = events.get(position);
|
||||||
|
holder.title.setText(event.getTitle());
|
||||||
|
holder.time.setText(event.getTime());
|
||||||
|
holder.channel.setText(event.getChannelName());
|
||||||
|
holder.status.setText(buildStatusText(event));
|
||||||
|
holder.itemView.setOnClickListener(v -> {
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onEventClick(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return events.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class EventViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
final TextView title;
|
||||||
|
final TextView time;
|
||||||
|
final TextView channel;
|
||||||
|
final TextView status;
|
||||||
|
|
||||||
|
EventViewHolder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
title = itemView.findViewById(R.id.event_title);
|
||||||
|
time = itemView.findViewById(R.id.event_time);
|
||||||
|
channel = itemView.findViewById(R.id.event_channel);
|
||||||
|
status = itemView.findViewById(R.id.event_status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildStatusText(EventItem event) {
|
||||||
|
long start = event.getStartMillis();
|
||||||
|
if (start <= 0) {
|
||||||
|
return event.getStatus();
|
||||||
|
}
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long diff = start - now;
|
||||||
|
if (diff > 0) {
|
||||||
|
long hours = diff / 3600000;
|
||||||
|
long minutes = (diff % 3600000) / 60000;
|
||||||
|
if (hours > 0) {
|
||||||
|
return String.format(Locale.getDefault(), "En %dh %02dm", hours, minutes);
|
||||||
|
} else {
|
||||||
|
return String.format(Locale.getDefault(), "En %d min", Math.max(1, minutes));
|
||||||
|
}
|
||||||
|
} else if (Math.abs(diff) <= 2 * 3600000L) {
|
||||||
|
return "En vivo";
|
||||||
|
} else {
|
||||||
|
return "Finalizado";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
app/src/main/java/com/streamplayer/EventItem.java
Normal file
49
app/src/main/java/com/streamplayer/EventItem.java
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package com.streamplayer;
|
||||||
|
|
||||||
|
public class EventItem {
|
||||||
|
private final String title;
|
||||||
|
private final String time;
|
||||||
|
private final String category;
|
||||||
|
private final String status;
|
||||||
|
private final String pageUrl;
|
||||||
|
private final String channelName;
|
||||||
|
private final long startMillis;
|
||||||
|
|
||||||
|
public EventItem(String title, String time, String category, String status, String pageUrl, String channelName, long startMillis) {
|
||||||
|
this.title = title;
|
||||||
|
this.time = time;
|
||||||
|
this.category = category;
|
||||||
|
this.status = status;
|
||||||
|
this.pageUrl = pageUrl;
|
||||||
|
this.channelName = channelName;
|
||||||
|
this.startMillis = startMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPageUrl() {
|
||||||
|
return pageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChannelName() {
|
||||||
|
return channelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStartMillis() {
|
||||||
|
return startMillis;
|
||||||
|
}
|
||||||
|
}
|
||||||
148
app/src/main/java/com/streamplayer/EventRepository.java
Normal file
148
app/src/main/java/com/streamplayer/EventRepository.java
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package com.streamplayer;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class EventRepository {
|
||||||
|
|
||||||
|
private static final String PREFS_NAME = "events_cache";
|
||||||
|
private static final String KEY_JSON = "json";
|
||||||
|
private static final String KEY_TIMESTAMP = "timestamp";
|
||||||
|
private static final long CACHE_DURATION = 24L * 60 * 60 * 1000; // 24 horas
|
||||||
|
private static final String EVENTS_URL = "https://streamtpmedia.com/eventos.json";
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void onSuccess(List<EventItem> events);
|
||||||
|
|
||||||
|
void onError(String message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadEvents(Context context, boolean forceRefresh, Callback callback) {
|
||||||
|
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
|
long last = prefs.getLong(KEY_TIMESTAMP, 0);
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (!forceRefresh && now - last < CACHE_DURATION) {
|
||||||
|
String cachedJson = prefs.getString(KEY_JSON, null);
|
||||||
|
if (cachedJson != null) {
|
||||||
|
try {
|
||||||
|
callback.onSuccess(parseEvents(cachedJson));
|
||||||
|
return;
|
||||||
|
} catch (JSONException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
String json = downloadJson();
|
||||||
|
List<EventItem> events = parseEvents(json);
|
||||||
|
prefs.edit().putString(KEY_JSON, json).putLong(KEY_TIMESTAMP, System.currentTimeMillis()).apply();
|
||||||
|
callback.onSuccess(events);
|
||||||
|
} catch (IOException | JSONException e) {
|
||||||
|
String cachedJson = prefs.getString(KEY_JSON, null);
|
||||||
|
if (cachedJson != null) {
|
||||||
|
try {
|
||||||
|
callback.onSuccess(parseEvents(cachedJson));
|
||||||
|
return;
|
||||||
|
} catch (JSONException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback.onError(e.getMessage() != null ? e.getMessage() : "Error desconocido");
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String downloadJson() throws IOException {
|
||||||
|
URL url = new URL(EVENTS_URL);
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
connection.setConnectTimeout(15000);
|
||||||
|
connection.setReadTimeout(15000);
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
builder.append(line);
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
} finally {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<EventItem> parseEvents(String json) throws JSONException {
|
||||||
|
JSONArray array = new JSONArray(json);
|
||||||
|
List<EventItem> events = new ArrayList<>();
|
||||||
|
for (int i = 0; i < array.length(); i++) {
|
||||||
|
JSONObject obj = array.getJSONObject(i);
|
||||||
|
String title = obj.optString("title");
|
||||||
|
String time = obj.optString("time");
|
||||||
|
String category = obj.optString("category");
|
||||||
|
String status = obj.optString("status");
|
||||||
|
String link = obj.optString("link");
|
||||||
|
String normalized = normalizeLink(link);
|
||||||
|
long startMillis = parseEventTime(time);
|
||||||
|
events.add(new EventItem(title, time, category, status, normalized, extractChannelName(link), startMillis));
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(events);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeLink(String link) {
|
||||||
|
if (link == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return link.replace("global1.php", "global2.php");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractChannelName(String link) {
|
||||||
|
if (link == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int idx = link.indexOf("stream=");
|
||||||
|
if (idx == -1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return link.substring(idx + 7).replace("_", " ").toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long parseEventTime(String time) {
|
||||||
|
if (time == null || time.isEmpty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
|
||||||
|
LocalTime localTime = LocalTime.parse(time.trim(), formatter);
|
||||||
|
ZoneId zone = ZoneId.of("America/Argentina/Buenos_Aires");
|
||||||
|
LocalDate today = LocalDate.now(zone);
|
||||||
|
ZonedDateTime start = ZonedDateTime.of(LocalDateTime.of(today, localTime), zone);
|
||||||
|
ZonedDateTime now = ZonedDateTime.now(zone);
|
||||||
|
if (start.isBefore(now.minusHours(12))) {
|
||||||
|
start = start.plusDays(1);
|
||||||
|
}
|
||||||
|
return start.toInstant().toEpochMilli();
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,157 +1,116 @@
|
|||||||
package com.streamplayer;
|
package com.streamplayer;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.StrictMode;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import java.util.List;
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
|
||||||
import com.google.android.exoplayer2.PlaybackException;
|
|
||||||
import com.google.android.exoplayer2.Player;
|
|
||||||
import com.google.android.exoplayer2.ui.PlayerView;
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private ExoPlayer player;
|
private RecyclerView channelGrid;
|
||||||
private PlayerView playerView;
|
private RecyclerView eventsList;
|
||||||
private ProgressBar loadingIndicator;
|
private View eventsContainer;
|
||||||
private TextView errorMessage;
|
private ProgressBar eventsProgress;
|
||||||
|
private TextView eventsError;
|
||||||
|
private Button tabChannels;
|
||||||
|
private Button tabEvents;
|
||||||
|
|
||||||
private static final String STREAM_PAGE_URL = "https://streamtpmedia.com/global2.php?stream=espn";
|
private EventAdapter eventAdapter;
|
||||||
|
private EventRepository eventRepository;
|
||||||
|
private boolean eventsLoaded = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Configurar política de red para allow cleartext traffic
|
|
||||||
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
|
|
||||||
StrictMode.setThreadPolicy(policy);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
initViews();
|
channelGrid = findViewById(R.id.channel_grid);
|
||||||
|
eventsList = findViewById(R.id.events_list);
|
||||||
|
eventsContainer = findViewById(R.id.events_container);
|
||||||
|
eventsProgress = findViewById(R.id.events_progress);
|
||||||
|
eventsError = findViewById(R.id.events_error);
|
||||||
|
tabChannels = findViewById(R.id.tab_channels);
|
||||||
|
tabEvents = findViewById(R.id.tab_events);
|
||||||
|
|
||||||
// Configurar DNS de Google para streaming
|
channelGrid.setLayoutManager(new GridLayoutManager(this, getSpanCount()));
|
||||||
DNSSetter.configureDNSToGoogle(this);
|
channelGrid.setHasFixedSize(true);
|
||||||
|
ChannelAdapter channelAdapter = new ChannelAdapter(
|
||||||
|
ChannelRepository.getChannels(),
|
||||||
|
channel -> openPlayer(channel.getName(), channel.getPageUrl()));
|
||||||
|
channelGrid.setAdapter(channelAdapter);
|
||||||
|
|
||||||
initializePlayer();
|
eventsList.setLayoutManager(new GridLayoutManager(this, 1));
|
||||||
|
eventAdapter = new EventAdapter(event -> openPlayer(event.getTitle(), event.getPageUrl()));
|
||||||
|
eventsList.setAdapter(eventAdapter);
|
||||||
|
|
||||||
|
eventRepository = new EventRepository();
|
||||||
|
|
||||||
|
tabChannels.setOnClickListener(v -> showChannels());
|
||||||
|
tabEvents.setOnClickListener(v -> showEvents());
|
||||||
|
|
||||||
|
showChannels();
|
||||||
|
channelGrid.post(channelGrid::requestFocus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initViews() {
|
private void showChannels() {
|
||||||
playerView = findViewById(R.id.player_view);
|
channelGrid.setVisibility(View.VISIBLE);
|
||||||
loadingIndicator = findViewById(R.id.loading_indicator);
|
eventsContainer.setVisibility(View.GONE);
|
||||||
errorMessage = findViewById(R.id.error_message);
|
tabChannels.setSelected(true);
|
||||||
|
tabEvents.setSelected(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializePlayer() {
|
private void showEvents() {
|
||||||
showLoading(true);
|
channelGrid.setVisibility(View.GONE);
|
||||||
new Thread(() -> {
|
eventsContainer.setVisibility(View.VISIBLE);
|
||||||
try {
|
tabChannels.setSelected(false);
|
||||||
String resolvedUrl = StreamUrlResolver.resolve(STREAM_PAGE_URL);
|
tabEvents.setSelected(true);
|
||||||
runOnUiThread(() -> startPlayback(resolvedUrl));
|
if (!eventsLoaded) {
|
||||||
} catch (Exception e) {
|
loadEvents(false);
|
||||||
runOnUiThread(() -> showError("Error al obtener stream: " + e.getMessage()));
|
|
||||||
}
|
}
|
||||||
}).start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startPlayback(String streamUrl) {
|
private void loadEvents(boolean forceRefresh) {
|
||||||
try {
|
eventsProgress.setVisibility(View.VISIBLE);
|
||||||
releasePlayer();
|
eventsError.setVisibility(View.GONE);
|
||||||
player = new ExoPlayer.Builder(this).build();
|
eventsList.setVisibility(View.GONE);
|
||||||
playerView.setPlayer(player);
|
eventRepository.loadEvents(this, forceRefresh, new EventRepository.Callback() {
|
||||||
|
|
||||||
player.addListener(new Player.Listener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackStateChanged(int playbackState) {
|
public void onSuccess(List<EventItem> events) {
|
||||||
switch (playbackState) {
|
runOnUiThread(() -> {
|
||||||
case Player.STATE_BUFFERING:
|
eventsProgress.setVisibility(View.GONE);
|
||||||
showLoading(true);
|
eventAdapter.submitList(events);
|
||||||
break;
|
eventsList.setVisibility(View.VISIBLE);
|
||||||
case Player.STATE_READY:
|
eventsLoaded = true;
|
||||||
showLoading(false);
|
});
|
||||||
break;
|
|
||||||
case Player.STATE_ENDED:
|
|
||||||
// Video terminado
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(PlaybackException error) {
|
public void onError(String message) {
|
||||||
showError("Error al reproducir: " + error.getMessage());
|
runOnUiThread(() -> {
|
||||||
|
eventsProgress.setVisibility(View.GONE);
|
||||||
|
eventsError.setText("No se pudieron cargar los eventos: " + message);
|
||||||
|
eventsError.setVisibility(View.VISIBLE);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
MediaItem mediaItem = MediaItem.fromUri(streamUrl);
|
|
||||||
player.setMediaItem(mediaItem);
|
|
||||||
player.prepare();
|
|
||||||
player.setPlayWhenReady(true);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
showError("Error al inicializar reproductor: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showLoading(boolean show) {
|
private void openPlayer(String name, String pageUrl) {
|
||||||
loadingIndicator.setVisibility(show ? View.VISIBLE : View.GONE);
|
Intent intent = new Intent(MainActivity.this, PlayerActivity.class);
|
||||||
errorMessage.setVisibility(View.GONE);
|
intent.putExtra(PlayerActivity.EXTRA_CHANNEL_NAME, name);
|
||||||
playerView.setVisibility(show ? View.GONE : View.VISIBLE);
|
intent.putExtra(PlayerActivity.EXTRA_CHANNEL_URL, pageUrl);
|
||||||
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showError(String message) {
|
private int getSpanCount() {
|
||||||
loadingIndicator.setVisibility(View.GONE);
|
return getResources().getInteger(R.integer.channel_grid_span);
|
||||||
playerView.setVisibility(View.GONE);
|
|
||||||
errorMessage.setVisibility(View.VISIBLE);
|
|
||||||
errorMessage.setText(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
if (player != null) {
|
|
||||||
playerView.onResume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
if (player != null) {
|
|
||||||
playerView.onResume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
if (player != null) {
|
|
||||||
playerView.onPause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
if (player != null) {
|
|
||||||
playerView.onPause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
releasePlayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void releasePlayer() {
|
|
||||||
if (player != null) {
|
|
||||||
player.release();
|
|
||||||
player = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
276
app/src/main/java/com/streamplayer/PlayerActivity.java
Normal file
276
app/src/main/java/com/streamplayer/PlayerActivity.java
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
package com.streamplayer;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.StrictMode;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
public static final String EXTRA_CHANNEL_NAME = "extra_channel_name";
|
||||||
|
public static final String EXTRA_CHANNEL_URL = "extra_channel_url";
|
||||||
|
|
||||||
|
private PlayerView playerView;
|
||||||
|
private ProgressBar loadingIndicator;
|
||||||
|
private TextView errorMessage;
|
||||||
|
private TextView channelLabel;
|
||||||
|
private Button closeButton;
|
||||||
|
private View playerToolbar;
|
||||||
|
|
||||||
|
private ExoPlayer player;
|
||||||
|
private String channelName;
|
||||||
|
private String channelUrl;
|
||||||
|
private boolean overlayVisible = true;
|
||||||
|
private OkHttpClient okHttpClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
StrictMode.setThreadPolicy(
|
||||||
|
new StrictMode.ThreadPolicy.Builder().permitAll().build()
|
||||||
|
);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_player);
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
if (intent == null) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
channelName = intent.getStringExtra(EXTRA_CHANNEL_NAME);
|
||||||
|
channelUrl = intent.getStringExtra(EXTRA_CHANNEL_URL);
|
||||||
|
|
||||||
|
if (channelName == null || channelUrl == null) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initViews();
|
||||||
|
channelLabel.setText(channelName);
|
||||||
|
|
||||||
|
DNSSetter.configureDNSToGoogle(this);
|
||||||
|
loadChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViews() {
|
||||||
|
playerView = findViewById(R.id.player_view);
|
||||||
|
loadingIndicator = findViewById(R.id.loading_indicator);
|
||||||
|
errorMessage = findViewById(R.id.error_message);
|
||||||
|
channelLabel = findViewById(R.id.player_channel_label);
|
||||||
|
closeButton = findViewById(R.id.close_button);
|
||||||
|
playerToolbar = findViewById(R.id.player_toolbar);
|
||||||
|
|
||||||
|
closeButton.setOnClickListener(v -> finish());
|
||||||
|
playerView.setOnClickListener(v -> toggleOverlay());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadChannel() {
|
||||||
|
showLoading(true);
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
String resolvedUrl = StreamUrlResolver.resolve(channelUrl);
|
||||||
|
runOnUiThread(() -> startPlayback(resolvedUrl));
|
||||||
|
} catch (Exception e) {
|
||||||
|
runOnUiThread(() -> showError("Error al obtener stream: " + e.getMessage()));
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startPlayback(String streamUrl) {
|
||||||
|
try {
|
||||||
|
releasePlayer();
|
||||||
|
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() {
|
||||||
|
@Override
|
||||||
|
public void onPlaybackStateChanged(int playbackState) {
|
||||||
|
if (playbackState == Player.STATE_READY) {
|
||||||
|
showLoading(false);
|
||||||
|
} else if (playbackState == Player.STATE_BUFFERING) {
|
||||||
|
showLoading(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayerError(PlaybackException error) {
|
||||||
|
String detail = error.getCause() != null ?
|
||||||
|
error.getCause().getMessage() : "";
|
||||||
|
showError("Error al reproducir: " + error.getMessage() + " " + detail);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
MediaItem mediaItem = MediaItem.fromUri(streamUrl);
|
||||||
|
player.setMediaSource(buildMediaSource(mediaItem));
|
||||||
|
player.prepare();
|
||||||
|
player.setPlayWhenReady(true);
|
||||||
|
setOverlayVisible(false);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
showError("Error al inicializar reproductor: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showLoading(boolean show) {
|
||||||
|
loadingIndicator.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||||
|
errorMessage.setVisibility(View.GONE);
|
||||||
|
playerView.setVisibility(show ? View.GONE : View.VISIBLE);
|
||||||
|
if (show) {
|
||||||
|
setOverlayVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showError(String message) {
|
||||||
|
loadingIndicator.setVisibility(View.GONE);
|
||||||
|
playerView.setVisibility(View.GONE);
|
||||||
|
errorMessage.setVisibility(View.VISIBLE);
|
||||||
|
errorMessage.setText(message);
|
||||||
|
setOverlayVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releasePlayer() {
|
||||||
|
if (player != null) {
|
||||||
|
player.release();
|
||||||
|
player = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
if (player != null) {
|
||||||
|
playerView.onResume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (player != null) {
|
||||||
|
playerView.onResume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
if (player != null) {
|
||||||
|
playerView.onPause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
if (player != null) {
|
||||||
|
playerView.onPause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
releasePlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleOverlay() {
|
||||||
|
setOverlayVisible(!overlayVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOverlayVisible(boolean visible) {
|
||||||
|
overlayVisible = visible;
|
||||||
|
playerToolbar.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (!overlayVisible) {
|
||||||
|
setOverlayVisible(true);
|
||||||
|
} else {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/src/main/java/com/streamplayer/StreamChannel.java
Normal file
19
app/src/main/java/com/streamplayer/StreamChannel.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package com.streamplayer;
|
||||||
|
|
||||||
|
public class StreamChannel {
|
||||||
|
private final String name;
|
||||||
|
private final String pageUrl;
|
||||||
|
|
||||||
|
public StreamChannel(String name, String pageUrl) {
|
||||||
|
this.name = name;
|
||||||
|
this.pageUrl = pageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPageUrl() {
|
||||||
|
return pageUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
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>
|
||||||
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>
|
||||||
33
app/src/main/res/drawable/bg_tab_selector.xml
Normal file
33
app/src/main/res/drawable/bg_tab_selector.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?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="#18d763" />
|
||||||
|
<corners android:radius="20dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:state_focused="true">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#5522c1ff" />
|
||||||
|
<corners android:radius="20dp" />
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="#88FFFFFF" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:state_pressed="true">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#3322c1ff" />
|
||||||
|
<corners android:radius="20dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#222222" />
|
||||||
|
<corners android:radius="20dp" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="#44FFFFFF" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
13
app/src/main/res/drawable/ic_channel_default.xml
Normal file
13
app/src/main/res/drawable/ic_channel_default.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M21,6h-7.59l2.3,-2.29c0.63,-0.63 0.19,-1.71 -0.7,-1.71H8.99c-0.89,0 -1.33,1.08 -0.7,1.71L10.59,6H3c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h18c1.11,0 2,-0.9 2,-2L23,8c0,-1.1 -0.89,-2 -2,-2zM21,18H3L3,8h18v10z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M9,10h2v6L9,16zM13,10h2v6h-2z" />
|
||||||
|
</vector>
|
||||||
@@ -7,35 +7,111 @@
|
|||||||
android:background="@color/black"
|
android:background="@color/black"
|
||||||
tools:context=".MainActivity">
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
<com.google.android.exoplayer2.ui.PlayerView
|
<TextView
|
||||||
android:id="@+id/player_view"
|
android:id="@+id/title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:resize_mode="fill"
|
|
||||||
app:use_controller="true" />
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/loading_indicator"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:indeterminateTint="@color/white"
|
android:layout_marginStart="16dp"
|
||||||
android:visibility="visible"
|
android:layout_marginTop="24dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:layout_marginEnd="16dp"
|
||||||
|
android:text="StreamPlayer"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textStyle="bold"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/error_message"
|
android:id="@+id/tabs_container"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Error al cargar el stream"
|
android:layout_marginStart="16dp"
|
||||||
android:textColor="@color/white"
|
android:layout_marginTop="16dp"
|
||||||
android:textSize="16sp"
|
android:layout_marginEnd="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/title">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/tab_channels"
|
||||||
|
style="?android:attr/buttonBarButtonStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_tab_selector"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:text="Canales"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/tab_events"
|
||||||
|
style="?android:attr/buttonBarButtonStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_tab_selector"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:text="Eventos"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/channel_grid"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_margin="12dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tabs_container"
|
||||||
|
tools:listitem="@layout/item_channel" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/events_container"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_margin="12dp"
|
||||||
|
android:orientation="vertical"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toBottomOf="@id/tabs_container">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/events_progress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/events_error"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="No se pudieron cargar los eventos" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/events_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
tools:listitem="@layout/item_event" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
73
app/src/main/res/layout/activity_player.xml
Normal file
73
app/src/main/res/layout/activity_player.xml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/black"
|
||||||
|
tools:context=".PlayerActivity">
|
||||||
|
|
||||||
|
<com.google.android.exoplayer2.ui.PlayerView
|
||||||
|
android:id="@+id/player_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:resize_mode="fill"
|
||||||
|
app:use_controller="true" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/player_toolbar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:background="#66000000"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="12dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/player_channel_label"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Canal"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/close_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Elegir otro"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/loading_indicator"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminateTint="@color/white"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/error_message"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Error al reproducir" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
33
app/src/main/res/layout/item_channel.xml
Normal file
33
app/src/main/res/layout/item_channel.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="6dp"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/channel_icon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:contentDescription="@string/app_name"
|
||||||
|
android:tint="@color/white"
|
||||||
|
android:src="@drawable/ic_channel_default" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/channel_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
56
app/src/main/res/layout/item_event.xml
Normal file
56
app/src/main/res/layout/item_event.xml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:background="@drawable/bg_channel_item_selector"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/event_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Partido" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/event_time"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="20:00" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/event_channel"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="ESPN" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/event_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:textColor="#18d763"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="En vivo" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
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