
Mobile Phone Se Android Application Kaise Banaye
Aaj hum apne mobile phone ki madad se ek simple aur professional Android IPTV Application banana seekhenge.
Is tutorial mein hum Android application kaise banayi jaati hai, woh step by step detail mein samjhenge. Yeh tutorial beginners ko dhyan mein rakhkar banaya gaya hai, taaki kisi bhi beginner ko samajhne mein koi dikkat na ho.
Step 1: Android Code Studio Mein Naya Project Banana
Agar aapke paas Android Code Studio nahi hai, to aap ise hamare Telegram channel se download kar sakte hain.
Sabse pehle Android Code Studio application ko open karein. App open hone ke baad Plus (+) / Create Project button par click karke ek naya project create karein.
Project create karte waqt Basic Activity select karein. Iske baad project ka naam set karein aur package name com.my.app rakhein.
Ab language mein Java select karein aur Minimum SDK ke liye API 21 select karein. Sabhi settings complete karne ke baad Create button par click karein.
Project Ka Structure
Ab humein apne project ka structure banana hai. Project ka structure kuch is tarah hona chahiye:
DesiIPTV/
├── .gitignore
├── README.md
├── build.gradle
├── settings.gradle
├── gradle.properties
└── app/
├── build.gradle
└── src/
└── main/
├── AndroidManifest.xml
├── java/
│ └── com/
│ └── my/
│ └── iptv/
│ ├── MainActivity.java
│ ├── PlayerActivity.java
│ ├── Channel.java
│ ├── ChannelAdapter.java
│ ├── M3UParser.java
│ └── FavoritesManager.java
└── res/
├── drawable/
│ ├── bg_card.xml
│ ├── bg_logo.xml
│ ├── bg_search.xml
│ ├── bg_topbar.xml
│ ├── ic_back.xml
│ ├── ic_fullscreen.xml
│ ├── ic_launcher.xml
│ ├── ic_placeholder.xml
│ ├── ic_star.xml
│ └── ic_star_border.xml
├── layout/
│ ├── activity_main.xml
│ ├── activity_player.xml
│ └── item_channel.xml
└── values/
├── colors.xml
├── strings.xml
└── themes.xml
Ab niche se har ek file ka code diya jayega. Aapko apne project ke structure ke according har file mein uska respective code paste karna hai.
Code paste karte waqt dhyan rakhein ki file ka naam aur folder location bilkul sahi ho. Agar file ka naam ya location galat hui, to application mein error aa sakta hai.
MainActivity.java code
package com.my.iptv;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.chip.ChipGroup;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity {
private static final String PLAYLIST_URL = "https://iptv-org.github.io/iptv/index.m3u";
private static final String ALL_CATEGORIES = "All Categories";
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private final List<Channel> allChannels = new ArrayList<Channel>();
private final List<String> categoryNames = new ArrayList<String>();
private RecyclerView recyclerView;
private GridLayoutManager layoutManager;
private ProgressBar progressBar;
private View errorLayout;
private TextView tvError;
private TextView tvEmpty;
private TextView tvCount;
private Button btnRetry;
private SearchView searchView;
private ChipGroup chipGroup;
private Spinner spinnerCategory;
private ChannelAdapter adapter;
private ArrayAdapter<String> categoryAdapter;
private FavoritesManager favorites;
private String searchQuery = "";
private String selectedCategory = ALL_CATEGORIES;
private boolean loaded = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
favorites = new FavoritesManager(this);
recyclerView = findViewById(R.id.recyclerView);
progressBar = findViewById(R.id.progressBar);
errorLayout = findViewById(R.id.errorLayout);
tvError = findViewById(R.id.tvError);
tvEmpty = findViewById(R.id.tvEmpty);
tvCount = findViewById(R.id.tvCount);
btnRetry = findViewById(R.id.btnRetry);
searchView = findViewById(R.id.searchView);
chipGroup = findViewById(R.id.chipGroup);
spinnerCategory = findViewById(R.id.spinnerCategory);
adapter = new ChannelAdapter(favorites, new ChannelAdapter.Listener() {
@Override
public void onChannelClick(Channel channel) {
Intent intent = new Intent(MainActivity.this, PlayerActivity.class);
intent.putExtra(PlayerActivity.EXTRA_NAME, channel.getName());
intent.putExtra(PlayerActivity.EXTRA_URL, channel.getUrl());
startActivity(intent);
}
@Override
public void onFavoriteClick(Channel channel, int position) {
favorites.toggle(channel.getUrl());
if (chipGroup.getCheckedChipId() == R.id.chipFavorites) {
applyFilters();
} else {
adapter.notifyItemChanged(position);
}
}
});
layoutManager = new GridLayoutManager(this, computeSpanCount());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.setAdapter(adapter);
categoryNames.add(ALL_CATEGORIES);
categoryAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, categoryNames);
categoryAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerCategory.setAdapter(categoryAdapter);
spinnerCategory.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position >= 0 && position < categoryNames.size()) {
selectedCategory = categoryNames.get(position);
applyFilters();
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
chipGroup.setOnCheckedStateChangeListener(new ChipGroup.OnCheckedStateChangeListener() {
@Override
public void onCheckedChanged(ChipGroup group, List<Integer> checkedIds) {
applyFilters();
}
});
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
searchQuery = newText == null ? "" : newText;
applyFilters();
return true;
}
});
btnRetry.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadPlaylist();
}
});
loadPlaylist();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
layoutManager.setSpanCount(computeSpanCount());
}
private int computeSpanCount() {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE
? 2 : 1;
}
private void loadPlaylist() {
showLoading();
if (!isNetworkAvailable()) {
showError(getString(R.string.error_network));
return;
}
executor.execute(new Runnable() {
@Override
public void run() {
try {
final List<Channel> channels = M3UParser.fetchAndParse(PLAYLIST_URL);
mainHandler.post(new Runnable() {
@Override
public void run() {
onPlaylistLoaded(channels);
}
});
} catch (final Exception e) {
mainHandler.post(new Runnable() {
@Override
public void run() {
onPlaylistFailed(e);
}
});
}
}
});
}
private void onPlaylistLoaded(List<Channel> channels) {
if (isFinishing() || isDestroyed()) {
return;
}
Collections.sort(channels, new Comparator<Channel>() {
@Override
public int compare(Channel a, Channel b) {
return a.getName().compareToIgnoreCase(b.getName());
}
});
allChannels.clear();
allChannels.addAll(channels);
if (allChannels.isEmpty()) {
showError(getString(R.string.no_channels));
return;
}
TreeSet<String> categories = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
for (int i = 0; i < allChannels.size(); i++) {
String[] parts = allChannels.get(i).getCategory().split(";");
for (int j = 0; j < parts.length; j++) {
String c = parts[j].trim();
if (c.length() > 0) {
categories.add(c);
}
}
}
categoryNames.clear();
categoryNames.add(ALL_CATEGORIES);
categoryNames.addAll(categories);
categoryAdapter.notifyDataSetChanged();
selectedCategory = ALL_CATEGORIES;
spinnerCategory.setSelection(0);
loaded = true;
progressBar.setVisibility(View.GONE);
errorLayout.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
applyFilters();
}
private void onPlaylistFailed(Exception e) {
if (isFinishing() || isDestroyed()) {
return;
}
if (e instanceof UnknownHostException || e instanceof SocketTimeoutException) {
showError(getString(R.string.error_network));
} else {
showError(getString(R.string.error_playlist) + "\n(" + e.getMessage() + ")");
}
}
private void applyFilters() {
if (!loaded) {
return;
}
String query = searchQuery.toLowerCase(Locale.ROOT).trim();
int checked = chipGroup.getCheckedChipId();
boolean all = selectedCategory.equals(ALL_CATEGORIES);
List<Channel> result = new ArrayList<Channel>();
for (int i = 0; i < allChannels.size(); i++) {
Channel c = allChannels.get(i);
if (checked == R.id.chipIndia && !"IN".equals(c.getCountry())) {
continue;
}
if (checked == R.id.chipNepal && !"NP".equals(c.getCountry())) {
continue;
}
if (checked == R.id.chipFavorites && !favorites.isFavorite(c.getUrl())) {
continue;
}
if (!all && !c.hasCategory(selectedCategory)) {
continue;
}
if (query.length() > 0
&& !c.getName().toLowerCase(Locale.ROOT).contains(query)) {
continue;
}
result.add(c);
}
adapter.setItems(result);
tvCount.setText(getString(R.string.channels_count, result.size()));
tvEmpty.setVisibility(result.isEmpty() ? View.VISIBLE : View.GONE);
}
private void showLoading() {
loaded = false;
progressBar.setVisibility(View.VISIBLE);
errorLayout.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
tvEmpty.setVisibility(View.GONE);
}
private void showError(String message) {
loaded = false;
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
tvEmpty.setVisibility(View.GONE);
tvError.setText(message);
errorLayout.setVisibility(View.VISIBLE);
}
@SuppressWarnings("deprecation")
private boolean isNetworkAvailable() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) {
return true; // let the real request decide
}
NetworkInfo info = cm.getActiveNetworkInfo();
return info != null && info.isConnected();
}
@Override
protected void onDestroy() {
super.onDestroy();
mainHandler.removeCallbacksAndMessages(null);
executor.shutdownNow();
}
}PlayerActivity.java code
package com.my.iptv;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.media3.common.MediaItem;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.ui.PlayerView;
public class PlayerActivity extends AppCompatActivity {
public static final String EXTRA_NAME = "extra_name";
public static final String EXTRA_URL = "extra_url";
private PlayerView playerView;
private ProgressBar progressBar;
private View errorLayout;
private View topBar;
private TextView tvError;
private TextView tvChannelName;
private Button btnRetry;
private ImageButton btnBack;
private ImageButton btnFullscreen;
private ExoPlayer player;
private String streamUrl;
private final Player.Listener playerListener = new Player.Listener() {
@Override
public void onPlaybackStateChanged(int state) {
if (state == Player.STATE_BUFFERING) {
progressBar.setVisibility(View.VISIBLE);
} else if (state == Player.STATE_READY) {
progressBar.setVisibility(View.GONE);
errorLayout.setVisibility(View.GONE);
} else if (state == Player.STATE_ENDED) {
progressBar.setVisibility(View.GONE);
}
}
@Override
public void onPlayerError(PlaybackException error) {
if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW
&& player != null) {
player.seekToDefaultPosition();
player.prepare();
return;
}
boolean network = error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED
|| error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT;
showError(getString(network ? R.string.error_network : R.string.error_stream));
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
playerView = findViewById(R.id.playerView);
progressBar = findViewById(R.id.progressBar);
errorLayout = findViewById(R.id.errorLayout);
topBar = findViewById(R.id.topBar);
tvError = findViewById(R.id.tvError);
tvChannelName = findViewById(R.id.tvChannelName);
btnRetry = findViewById(R.id.btnRetry);
btnBack = findViewById(R.id.btnBack);
btnFullscreen = findViewById(R.id.btnFullscreen);
String name = getIntent().getStringExtra(EXTRA_NAME);
streamUrl = getIntent().getStringExtra(EXTRA_URL);
tvChannelName.setText(name == null ? "" : name);
if (streamUrl == null || streamUrl.length() == 0) {
Toast.makeText(this, R.string.error_stream, Toast.LENGTH_LONG).show();
finish();
return;
}
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
btnFullscreen.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean landscape = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE;
setRequestedOrientation(landscape
? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
: ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
});
btnRetry.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
retry();
}
});
playerView.setControllerVisibilityListener(new PlayerView.ControllerVisibilityListener() {
@Override
public void onVisibilityChanged(int visibility) {
topBar.setVisibility(visibility);
}
});
applySystemUi(getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE);
}
@Override
protected void onStart() {
super.onStart();
if (streamUrl != null) {
initializePlayer();
}
}
@Override
protected void onStop() {
super.onStop();
releasePlayer();
}
@Override
protected void onDestroy() {
releasePlayer();
super.onDestroy();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
applySystemUi(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
}
private void applySystemUi(boolean landscape) {
WindowInsetsControllerCompat controller =
WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());
if (controller == null) {
return;
}
if (landscape) {
controller.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
controller.hide(WindowInsetsCompat.Type.systemBars());
} else {
controller.show(WindowInsetsCompat.Type.systemBars());
}
}
private void initializePlayer() {
if (player != null) {
return;
}
try {
DefaultHttpDataSource.Factory httpFactory = new DefaultHttpDataSource.Factory()
.setUserAgent("Mozilla/5.0 (Linux; Android) DesiIPTV/1.0")
.setAllowCrossProtocolRedirects(true)
.setConnectTimeoutMs(15000)
.setReadTimeoutMs(15000);
Context context = this;
player = new ExoPlayer.Builder(context)
.setMediaSourceFactory(
new DefaultMediaSourceFactory(context).setDataSourceFactory(httpFactory))
.build();
player.addListener(playerListener);
playerView.setPlayer(player);
startPlayback();
} catch (Exception e) {
showError(getString(R.string.error_stream));
}
}
private void startPlayback() {
if (player == null) {
return;
}
errorLayout.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
try {
player.setMediaItem(MediaItem.fromUri(Uri.parse(streamUrl)));
player.setPlayWhenReady(true);
player.prepare();
} catch (Exception e) {
showError(getString(R.string.error_stream));
}
}
private void retry() {
if (player == null) {
initializePlayer();
} else {
startPlayback();
}
}
private void showError(String message) {
progressBar.setVisibility(View.GONE);
tvError.setText(message);
errorLayout.setVisibility(View.VISIBLE);
}
private void releasePlayer() {
if (player != null) {
try {
player.removeListener(playerListener);
playerView.setPlayer(null);
player.release();
} catch (Exception ignored) {
}
player = null;
}
}
}Channel.java code
package com.my.iptv;
import java.io.Serializable;
public class Channel implements Serializable {
private static final long serialVersionUID = 1L;
private final String name;
private final String url;
private final String logo;
private final String country; // "IN" or "NP"
private final String category;
public Channel(String name, String url, String logo, String country, String category) {
this.name = name;
this.url = url;
this.logo = logo;
this.country = country;
this.category = category;
}
public String getName() {
return name;
}
public String getUrl() {
return url;
}
public String getLogo() {
return logo;
}
public String getCountry() {
return country;
}
public String getCategory() {
return category;
}
public String getCountryLabel() {
return "NP".equals(country) ? "Nepal" : "India";
}
/** group-title can hold several categories separated by ';'. */
public boolean hasCategory(String wanted) {
if (wanted == null || category == null) {
return false;
}
String[] parts = category.split(";");
for (int i = 0; i < parts.length; i++) {
if (parts[i].trim().equalsIgnoreCase(wanted)) {
return true;
}
}
return false;
}
}ChannelAdapter.java code
package com.my.iptv;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import java.util.List;
public class ChannelAdapter extends RecyclerView.Adapter<ChannelAdapter.ViewHolder> {
public interface Listener {
void onChannelClick(Channel channel);
void onFavoriteClick(Channel channel, int position);
}
private final List<Channel> items = new ArrayList<Channel>();
private final FavoritesManager favorites;
private final Listener listener;
public ChannelAdapter(FavoritesManager favorites, Listener listener) {
this.favorites = favorites;
this.listener = listener;
}
public void setItems(List<Channel> newItems) {
items.clear();
items.addAll(newItems);
notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_channel, parent, false);
final ViewHolder holder = new ViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getBindingAdapterPosition();
if (pos != RecyclerView.NO_POSITION && listener != null) {
listener.onChannelClick(items.get(pos));
}
}
});
holder.favorite.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getBindingAdapterPosition();
if (pos != RecyclerView.NO_POSITION && listener != null) {
listener.onFavoriteClick(items.get(pos), pos);
}
}
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Channel channel = items.get(position);
Context context = holder.itemView.getContext();
holder.name.setText(channel.getName());
holder.meta.setText(channel.getCategory() + " \u2022 " + channel.getCountryLabel());
holder.favorite.setImageResource(favorites.isFavorite(channel.getUrl())
? R.drawable.ic_star : R.drawable.ic_star_border);
String logo = channel.getLogo();
if (logo != null && logo.length() > 0) {
Glide.with(context)
.load(logo)
.placeholder(R.drawable.ic_placeholder)
.error(R.drawable.ic_placeholder)
.into(holder.logo);
} else {
Glide.with(context).clear(holder.logo);
holder.logo.setImageResource(R.drawable.ic_placeholder);
}
}
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
super.onViewRecycled(holder);
Glide.with(holder.itemView.getContext()).clear(holder.logo);
}
@Override
public int getItemCount() {
return items.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
final ImageView logo;
final TextView name;
final TextView meta;
final ImageButton favorite;
ViewHolder(View itemView) {
super(itemView);
logo = itemView.findViewById(R.id.ivLogo);
name = itemView.findViewById(R.id.tvName);
meta = itemView.findViewById(R.id.tvMeta);
favorite = itemView.findViewById(R.id.btnFavorite);
}
}
}M3UParser.java code
package com.my.iptv;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Streams an M3U playlist from the network (nothing is written to disk) and
* returns only Indian ("IN") and Nepali ("NP") channels.
*
* Country detection (metadata only, never channel names):
* 1. tvg-country attribute, if present.
* 2. Otherwise the country suffix of tvg-id (iptv-org format "ChannelName.in@SD").
*/
public class M3UParser {
private static final Pattern ATTRIBUTE =
Pattern.compile("([A-Za-z0-9_-]+)=\"([^\"]*)\"");
private static final String USER_AGENT = "Mozilla/5.0 (Linux; Android) DesiIPTV/1.0";
private M3UParser() {
}
private static class ExtInf {
String name = "";
String tvgId = "";
String logo = "";
String countryAttr = "";
String group = "";
}
public static List<Channel> fetchAndParse(String playlistUrl) throws IOException {
HttpURLConnection connection = null;
try {
URL url = new URL(playlistUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(15000);
connection.setReadTimeout(30000);
connection.setInstanceFollowRedirects(true);
connection.setRequestProperty("User-Agent", USER_AGENT);
int code = connection.getResponseCode();
if (code != HttpURLConnection.HTTP_OK) {
throw new IOException("HTTP error " + code);
}
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream(), "UTF-8"), 16384);
try {
return parse(reader);
} finally {
try {
reader.close();
} catch (IOException ignored) {
}
}
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
public static List<Channel> parse(BufferedReader reader) throws IOException {
List<Channel> result = new ArrayList<Channel>();
Set<String> seenUrls = new HashSet<String>();
ExtInf pending = null;
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.length() == 0) {
continue;
}
if (line.startsWith("#EXTINF")) {
pending = parseExtInf(line);
continue;
}
if (line.startsWith("#")) {
continue; // #EXTM3U, #EXTVLCOPT, comments...
}
if (pending != null) {
if (isValidStreamUrl(line) && seenUrls.add(line)) {
String country = resolveCountry(pending);
if (country != null) {
String name = pending.name.length() > 0 ? pending.name : "Unknown channel";
String group = pending.group.length() > 0 ? pending.group : "Uncategorized";
result.add(new Channel(name, line, pending.logo, country, group));
}
}
pending = null;
}
}
return result;
}
private static ExtInf parseExtInf(String line) {
ExtInf info = new ExtInf();
try {
// First comma that is NOT inside quotes separates attributes from the title.
boolean inQuotes = false;
int commaIndex = -1;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (c == '"') {
inQuotes = !inQuotes;
} else if (c == ',' && !inQuotes) {
commaIndex = i;
break;
}
}
String header = commaIndex >= 0 ? line.substring(0, commaIndex) : line;
if (commaIndex >= 0 && commaIndex + 1 < line.length()) {
info.name = line.substring(commaIndex + 1).trim();
}
Map<String, String> attrs = new HashMap<String, String>();
Matcher m = ATTRIBUTE.matcher(header);
while (m.find()) {
attrs.put(m.group(1).toLowerCase(Locale.ROOT), m.group(2).trim());
}
info.tvgId = valueOrEmpty(attrs.get("tvg-id"));
info.logo = valueOrEmpty(attrs.get("tvg-logo"));
info.countryAttr = valueOrEmpty(attrs.get("tvg-country"));
info.group = valueOrEmpty(attrs.get("group-title"));
if (info.name.length() == 0) {
info.name = valueOrEmpty(attrs.get("tvg-name"));
}
} catch (RuntimeException e) {
// Malformed entry: keep whatever was parsed, never crash.
}
return info;
}
private static String valueOrEmpty(String s) {
return s == null ? "" : s;
}
private static boolean isValidStreamUrl(String url) {
String lower = url.toLowerCase(Locale.ROOT);
return lower.startsWith("http://") || lower.startsWith("https://");
}
/** Returns "IN", "NP" or null if the entry is neither Indian nor Nepali. */
private static String resolveCountry(ExtInf info) {
if (info.countryAttr.length() > 0) {
return matchCountry(info.countryAttr);
}
return matchCountry(codeFromTvgId(info.tvgId));
}
private static String codeFromTvgId(String tvgId) {
if (tvgId == null || tvgId.length() == 0) {
return "";
}
String id = tvgId;
int at = id.indexOf('@');
if (at >= 0) {
id = id.substring(0, at);
}
int dot = id.lastIndexOf('.');
if (dot < 0 || dot == id.length() - 1) {
return "";
}
String suffix = id.substring(dot + 1);
return suffix.length() == 2 ? suffix : "";
}
private static String matchCountry(String raw) {
if (raw == null || raw.length() == 0) {
return null;
}
String[] tokens = raw.split("[;,|/]");
for (int i = 0; i < tokens.length; i++) {
String t = tokens[i].trim().toUpperCase(Locale.ROOT);
if (t.equals("IN") || t.equals("IND") || t.equals("INDIA")) {
return "IN";
}
if (t.equals("NP") || t.equals("NPL") || t.equals("NEPAL")) {
return "NP";
}
}
return null;
}
}FavoritesManager.java code
package com.my.iptv;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.HashSet;
import java.util.Set;
public class FavoritesManager {
private static final String PREFS_NAME = "iptv_prefs";
private static final String KEY_FAVORITES = "favorite_urls";
private final SharedPreferences prefs;
private final Set<String> cache;
public FavoritesManager(Context context) {
prefs = context.getApplicationContext()
.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
Set<String> saved = prefs.getStringSet(KEY_FAVORITES, new HashSet<String>());
cache = new HashSet<String>(saved);
}
public boolean isFavorite(String url) {
return url != null && cache.contains(url);
}
/** Returns the new favorite state. */
public boolean toggle(String url) {
if (url == null) {
return false;
}
boolean nowFavorite;
if (cache.contains(url)) {
cache.remove(url);
nowFavorite = false;
} else {
cache.add(url);
nowFavorite = true;
}
prefs.edit().putStringSet(KEY_FAVORITES, new HashSet<String>(cache)).apply();
return nowFavorite;
}
}Ab yahan se hum /res folder ki files ka code add karna shuru karenge. Neeche diye gaye code ko apne project ki respective files mein paste karein.
drawable/ bg_card.xml code
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/card" />
<corners android:radius="12dp" />
</shape>drawable/ bg_logo.xml code
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#ECEFF1" />
<corners android:radius="10dp" />
</shape>drawable/ bg_search.xml code
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/card" />
<stroke
android:width="1dp"
android:color="#CFD8DC" />
<corners android:radius="24dp" />
</shape>drawable/ bg_topbar.xml code
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="270"
android:endColor="#CC000000"
android:startColor="#00000000" />
</shape>drawable/ ic_back.xml code
<?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="#FFFFFF"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
</vector>drawable/ ic_fullscreen.xml code
<?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="#FFFFFF"
android:pathData="M7,14H5v5h5v-2H7v-3zM5,10h2V7h3V5H5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2V5h-5z" />
</vector>drawable/ ic_launcher.xml code
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#1E88E5"
android:pathData="M0,0h48v48h-48z" />
<path
android:fillColor="#FFFFFF"
android:pathData="M18,12L38,24L18,36z" />
</vector>drawable/ ic_placeholder.xml code
<?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="#90A4AE"
android:pathData="M21,3H3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,17H3V5h18v12z" />
</vector>drawable/ ic_star.xml code
<?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="#FFB300"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" />
</vector>drawable/ ic_star_border.xml code
<?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="#9E9E9E"
android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z" />
</vector>layout/ activity_main.xml code
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/primary"
android:padding="16dp"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="22sp"
android:textStyle="bold" />
<androidx.appcompat.widget.SearchView
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_margin="12dp"
android:background="@drawable/bg_search"
app:iconifiedByDefault="false"
app:queryHint="@string/search_hint" />
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
app:selectionRequired="true"
app:singleLine="true"
app:singleSelection="true">
<com.google.android.material.chip.Chip
android:id="@+id/chipAll"
style="@style/Widget.MaterialComponents.Chip.Choice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/chip_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chipIndia"
style="@style/Widget.MaterialComponents.Chip.Choice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/chip_india" />
<com.google.android.material.chip.Chip
android:id="@+id/chipNepal"
style="@style/Widget.MaterialComponents.Chip.Choice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/chip_nepal" />
<com.google.android.material.chip.Chip
android:id="@+id/chipFavorites"
style="@style/Widget.MaterialComponents.Chip.Choice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/chip_favorites" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<Spinner
android:id="@+id/spinnerCategory"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp" />
<TextView
android:id="@+id/tvCount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="4dp"
android:paddingEnd="16dp"
android:textColor="@color/text_secondary"
android:textSize="13sp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="8dp"
android:visibility="gone" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<TextView
android:id="@+id/tvEmpty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="24dp"
android:text="@string/no_results"
android:textColor="@color/text_secondary"
android:visibility="gone" />
<LinearLayout
android:id="@+id/errorLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:padding="24dp"
android:visibility="gone">
<TextView
android:id="@+id/tvError"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/text_primary"
android:textSize="15sp" />
<Button
android:id="@+id/btnRetry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/retry" />
</LinearLayout>
</FrameLayout>
</LinearLayout>layout/ activity_player.xml code
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:keepScreenOn="true">
<androidx.media3.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:resize_mode="fit"
app:show_buffering="never"
app:use_controller="true" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<LinearLayout
android:id="@+id/errorLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:padding="24dp"
android:visibility="gone">
<TextView
android:id="@+id/tvError"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/white"
android:textSize="16sp" />
<Button
android:id="@+id/btnRetry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/retry" />
</LinearLayout>
<LinearLayout
android:id="@+id/topBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="@drawable/bg_topbar"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp">
<ImageButton
android:id="@+id/btnBack"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/back"
android:src="@drawable/ic_back" />
<TextView
android:id="@+id/tvChannelName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
<ImageButton
android:id="@+id/btnFullscreen"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/fullscreen"
android:src="@drawable/ic_fullscreen" />
</LinearLayout>
</FrameLayout>layout/ item_channel.xml code
<?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_marginStart="12dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="6dp"
android:background="@drawable/bg_card"
android:elevation="2dp"
android:foreground="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:id="@+id/ivLogo"
android:layout_width="56dp"
android:layout_height="56dp"
android:background="@drawable/bg_logo"
android:contentDescription="@string/channel_logo"
android:padding="6dp"
android:scaleType="fitCenter" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tvName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@color/text_primary"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvMeta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/text_secondary"
android:textSize="13sp" />
</LinearLayout>
<ImageButton
android:id="@+id/btnFavorite"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/favorite"
android:src="@drawable/ic_star_border" />
</LinearLayout>values/ colors.xml code
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="primary">#1E88E5</color>
<color name="primary_dark">#1565C0</color>
<color name="accent">#FF7043</color>
<color name="background">#F3F5F9</color>
<color name="card">#FFFFFF</color>
<color name="text_primary">#212121</color>
<color name="text_secondary">#757575</color>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
</resources>values/ strings.xml code
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Desi IPTV</string>
<string name="search_hint">Search channels</string>
<string name="chip_all">All</string>
<string name="chip_india">India</string>
<string name="chip_nepal">Nepal</string>
<string name="chip_favorites">Favorites</string>
<string name="retry">Retry</string>
<string name="no_channels">No Indian or Nepali channels were found in the playlist.</string>
<string name="no_results">No channels match your filters.</string>
<string name="error_network">Network error. Check your internet connection and try again.</string>
<string name="error_playlist">Could not load the channel playlist.</string>
<string name="error_stream">This stream is unavailable right now. Try again or pick another channel.</string>
<string name="channels_count">%1$d channels</string>
<string name="back">Back</string>
<string name="fullscreen">Toggle full screen</string>
<string name="favorite">Favorite</string>
<string name="channel_logo">Channel logo</string>
</resources>values/ themes.xml code
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.IPTV" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryVariant">@color/primary_dark</item>
<item name="colorOnPrimary">@color/white</item>
<item name="colorSecondary">@color/accent</item>
<item name="android:statusBarColor">@color/primary_dark</item>
<item name="android:windowBackground">@color/background</item>
</style>
</resources>
0 Comments