Android: runtime server switching + fix createRoom endpoint
This commit is contained in:
parent
7b6bfbc935
commit
99bdd6b5dd
|
|
@ -41,6 +41,7 @@ import com.example.livestreaming.net.ApiClient;
|
|||
import com.example.livestreaming.net.ApiResponse;
|
||||
import com.example.livestreaming.net.CreateRoomRequest;
|
||||
import com.example.livestreaming.net.Room;
|
||||
import com.example.livestreaming.net.StreamConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -742,8 +743,32 @@ public class MainActivity extends AppCompatActivity {
|
|||
ApiResponse<Room> body = response.body();
|
||||
Room room = body != null ? body.getData() : null;
|
||||
if (!response.isSuccessful() || body == null || !body.isOk() || room == null) {
|
||||
String msg = body != null && !TextUtils.isEmpty(body.getMessage()) ? body.getMessage() : "创建失败";
|
||||
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
|
||||
String msg;
|
||||
if (!response.isSuccessful()) {
|
||||
String err = null;
|
||||
try {
|
||||
okhttp3.ResponseBody eb = response.errorBody();
|
||||
err = eb != null ? eb.string() : null;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
if (!TextUtils.isEmpty(err)) {
|
||||
msg = "HTTP " + response.code() + ":" + err;
|
||||
} else {
|
||||
msg = "HTTP " + response.code() + ":创建失败";
|
||||
}
|
||||
} else if (body == null) {
|
||||
msg = "服务返回空数据:创建失败";
|
||||
} else if (!body.isOk()) {
|
||||
String m = body.getMessage();
|
||||
if (!TextUtils.isEmpty(m)) {
|
||||
msg = m;
|
||||
} else {
|
||||
msg = "接口返回异常(code=" + body.getCode() + ")";
|
||||
}
|
||||
} else {
|
||||
msg = "创建失败:返回无房间数据";
|
||||
}
|
||||
Toast.makeText(MainActivity.this, "创建失败: " + msg, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -788,7 +813,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
String rtmp = room != null && room.getStreamUrls() != null ? room.getStreamUrls().getRtmp() : null;
|
||||
|
||||
// 直接使用服务器返回的RTMP地址
|
||||
String rtmpForObs = rtmp;
|
||||
String rtmpForObs = StreamConfig.rewriteStreamUrl(this, rtmp);
|
||||
|
||||
// 创建自定义弹窗布局
|
||||
View dialogView = getLayoutInflater().inflate(R.layout.dialog_stream_info, null);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.example.livestreaming.databinding.ActivityRoomDetailNewBinding;
|
|||
import com.example.livestreaming.net.ApiClient;
|
||||
import com.example.livestreaming.net.ApiResponse;
|
||||
import com.example.livestreaming.net.Room;
|
||||
import com.example.livestreaming.net.StreamConfig;
|
||||
|
||||
import tv.danmaku.ijk.media.player.IMediaPlayer;
|
||||
import tv.danmaku.ijk.media.player.IjkMediaPlayer;
|
||||
|
|
@ -382,6 +383,9 @@ public class RoomDetailActivity extends AppCompatActivity {
|
|||
if (TextUtils.isEmpty(playUrl)) playUrl = fallbackHlsUrl;
|
||||
}
|
||||
|
||||
playUrl = StreamConfig.rewriteStreamUrl(this, playUrl);
|
||||
fallbackHlsUrl = StreamConfig.rewriteStreamUrl(this, fallbackHlsUrl);
|
||||
|
||||
if (!TextUtils.isEmpty(playUrl)) {
|
||||
ensurePlayer(playUrl, fallbackHlsUrl);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,17 @@ package com.example.livestreaming;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.example.livestreaming.databinding.ActivitySettingsPageBinding;
|
||||
import com.example.livestreaming.net.ApiClient;
|
||||
import com.example.livestreaming.net.StreamConfig;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
|
@ -23,8 +28,11 @@ public class SettingsPageActivity extends AppCompatActivity {
|
|||
public static final String PAGE_CLEAR_CACHE = "clear_cache";
|
||||
public static final String PAGE_HELP = "help";
|
||||
public static final String PAGE_ABOUT = "about";
|
||||
public static final String PAGE_SERVER = "server";
|
||||
|
||||
private ActivitySettingsPageBinding binding;
|
||||
private MoreAdapter adapter;
|
||||
private String page;
|
||||
|
||||
public static void start(Context context, String page) {
|
||||
Intent intent = new Intent(context, SettingsPageActivity.class);
|
||||
|
|
@ -40,16 +48,52 @@ public class SettingsPageActivity extends AppCompatActivity {
|
|||
|
||||
binding.backButton.setOnClickListener(v -> finish());
|
||||
|
||||
String page = getIntent() != null ? getIntent().getStringExtra(EXTRA_PAGE) : null;
|
||||
page = getIntent() != null ? getIntent().getStringExtra(EXTRA_PAGE) : null;
|
||||
if (page == null) page = "";
|
||||
|
||||
String title = resolveTitle(page);
|
||||
binding.titleText.setText(title);
|
||||
|
||||
MoreAdapter adapter = new MoreAdapter(item -> {
|
||||
adapter = new MoreAdapter(item -> {
|
||||
if (item == null) return;
|
||||
if (item.getType() != MoreItem.Type.ROW) return;
|
||||
String t = item.getTitle() != null ? item.getTitle() : "";
|
||||
|
||||
if ("服务器设置".equals(t)) {
|
||||
SettingsPageActivity.start(this, PAGE_SERVER);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("API服务器".equals(t)) {
|
||||
showApiBaseUrlDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
if ("恢复默认API服务器".equals(t)) {
|
||||
ApiClient.clearCustomBaseUrl(getApplicationContext());
|
||||
ApiClient.getService(getApplicationContext());
|
||||
refresh();
|
||||
Toast.makeText(this, "已恢复默认API服务器", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if ("直播流服务器".equals(t)) {
|
||||
showStreamHostDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
if ("清除直播流覆写".equals(t)) {
|
||||
StreamConfig.clearStreamHostOverride(getApplicationContext());
|
||||
refresh();
|
||||
Toast.makeText(this, "已清除直播流覆写", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if ("返回".equals(t)) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(this, "点击:" + t, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
|
|
@ -73,6 +117,8 @@ public class SettingsPageActivity extends AppCompatActivity {
|
|||
return "帮助与反馈";
|
||||
case PAGE_ABOUT:
|
||||
return "关于";
|
||||
case PAGE_SERVER:
|
||||
return "服务器设置";
|
||||
default:
|
||||
return "设置";
|
||||
}
|
||||
|
|
@ -127,7 +173,169 @@ public class SettingsPageActivity extends AppCompatActivity {
|
|||
return list;
|
||||
}
|
||||
|
||||
list.add(MoreItem.row("返回", "", R.drawable.ic_arrow_back_24));
|
||||
if (PAGE_SERVER.equals(page)) {
|
||||
String apiCurrent = ApiClient.getCurrentBaseUrl(getApplicationContext());
|
||||
String apiMode = ApiClient.isUsingCustomBaseUrl(getApplicationContext()) ? "自定义" : "自动";
|
||||
String apiSub = (TextUtils.isEmpty(apiCurrent) ? "" : apiCurrent) + (TextUtils.isEmpty(apiMode) ? "" : ("(" + apiMode + ")"));
|
||||
|
||||
String streamHost = StreamConfig.getStreamHostOverride(getApplicationContext());
|
||||
String streamSub = TextUtils.isEmpty(streamHost) ? "未覆写(使用服务端返回)" : ("当前覆写:" + streamHost);
|
||||
|
||||
list.add(MoreItem.section("API"));
|
||||
list.add(MoreItem.row("API服务器", apiSub, R.drawable.ic_globe_24));
|
||||
list.add(MoreItem.row("恢复默认API服务器", ApiClient.getDefaultAutoBaseUrl(getApplicationContext()), R.drawable.ic_globe_24));
|
||||
|
||||
list.add(MoreItem.section("直播流"));
|
||||
list.add(MoreItem.row("直播流服务器", streamSub, R.drawable.ic_globe_24));
|
||||
list.add(MoreItem.row("清除直播流覆写", "恢复为服务端返回的地址", R.drawable.ic_globe_24));
|
||||
return list;
|
||||
}
|
||||
|
||||
list.add(MoreItem.section("通用"));
|
||||
list.add(MoreItem.row("服务器设置", "切换API与直播流地址", R.drawable.ic_globe_24));
|
||||
list.add(MoreItem.row("关于", "版本信息、协议", R.drawable.ic_menu_24));
|
||||
return list;
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
if (adapter == null) return;
|
||||
adapter.submitList(buildItems(page));
|
||||
}
|
||||
|
||||
private void showApiBaseUrlDialog() {
|
||||
String current = ApiClient.getCurrentBaseUrl(getApplicationContext());
|
||||
String[] history = ApiClient.getBaseUrlHistory(getApplicationContext());
|
||||
|
||||
List<String> options = new ArrayList<>();
|
||||
options.add("输入自定义");
|
||||
if (history != null && history.length > 0) options.add("从历史选择");
|
||||
options.add("恢复默认(自动)");
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("API服务器\n当前:" + (current != null ? current : ""))
|
||||
.setItems(options.toArray(new String[0]), (d, which) -> {
|
||||
String sel = options.get(which);
|
||||
if ("输入自定义".equals(sel)) {
|
||||
showApiBaseUrlInput();
|
||||
return;
|
||||
}
|
||||
if ("从历史选择".equals(sel)) {
|
||||
showApiBaseUrlHistory();
|
||||
return;
|
||||
}
|
||||
if ("恢复默认(自动)".equals(sel)) {
|
||||
ApiClient.clearCustomBaseUrl(getApplicationContext());
|
||||
ApiClient.getService(getApplicationContext());
|
||||
refresh();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("取消", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showApiBaseUrlInput() {
|
||||
EditText input = new EditText(this);
|
||||
input.setHint("例如:http://192.168.1.164:8081/");
|
||||
String current = ApiClient.getCurrentBaseUrl(getApplicationContext());
|
||||
if (!TextUtils.isEmpty(current)) input.setText(current);
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("输入API BaseUrl")
|
||||
.setView(input)
|
||||
.setNegativeButton("取消", null)
|
||||
.setPositiveButton("保存", (d, w) -> {
|
||||
String v = input.getText() != null ? input.getText().toString().trim() : "";
|
||||
if (TextUtils.isEmpty(v)) return;
|
||||
ApiClient.setCustomBaseUrl(getApplicationContext(), v);
|
||||
ApiClient.getService(getApplicationContext());
|
||||
refresh();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showApiBaseUrlHistory() {
|
||||
String[] history = ApiClient.getBaseUrlHistory(getApplicationContext());
|
||||
if (history == null || history.length == 0) {
|
||||
Toast.makeText(this, "暂无历史记录", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("选择历史API服务器")
|
||||
.setItems(history, (d, which) -> {
|
||||
String sel = history[which];
|
||||
if (TextUtils.isEmpty(sel)) return;
|
||||
ApiClient.setCustomBaseUrl(getApplicationContext(), sel);
|
||||
ApiClient.getService(getApplicationContext());
|
||||
refresh();
|
||||
})
|
||||
.setNegativeButton("取消", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showStreamHostDialog() {
|
||||
String current = StreamConfig.getStreamHostOverride(getApplicationContext());
|
||||
String[] history = StreamConfig.getStreamHostHistory(getApplicationContext());
|
||||
|
||||
List<String> options = new ArrayList<>();
|
||||
options.add("输入/修改");
|
||||
if (history != null && history.length > 0) options.add("从历史选择");
|
||||
options.add("清除覆写");
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("直播流服务器覆写\n当前:" + (!TextUtils.isEmpty(current) ? current : "未设置"))
|
||||
.setItems(options.toArray(new String[0]), (d, which) -> {
|
||||
String sel = options.get(which);
|
||||
if ("输入/修改".equals(sel)) {
|
||||
showStreamHostInput();
|
||||
return;
|
||||
}
|
||||
if ("从历史选择".equals(sel)) {
|
||||
showStreamHostHistory();
|
||||
return;
|
||||
}
|
||||
if ("清除覆写".equals(sel)) {
|
||||
StreamConfig.clearStreamHostOverride(getApplicationContext());
|
||||
refresh();
|
||||
}
|
||||
})
|
||||
.setNegativeButton("取消", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showStreamHostInput() {
|
||||
EditText input = new EditText(this);
|
||||
input.setHint("例如:192.168.1.164 或 192.168.1.164:1935");
|
||||
String current = StreamConfig.getStreamHostOverride(getApplicationContext());
|
||||
if (!TextUtils.isEmpty(current)) input.setText(current);
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("输入直播流服务器")
|
||||
.setView(input)
|
||||
.setNegativeButton("取消", null)
|
||||
.setPositiveButton("保存", (d, w) -> {
|
||||
String v = input.getText() != null ? input.getText().toString().trim() : "";
|
||||
if (TextUtils.isEmpty(v)) return;
|
||||
StreamConfig.setStreamHostOverride(getApplicationContext(), v);
|
||||
refresh();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showStreamHostHistory() {
|
||||
String[] history = StreamConfig.getStreamHostHistory(getApplicationContext());
|
||||
if (history == null || history.length == 0) {
|
||||
Toast.makeText(this, "暂无历史记录", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("选择历史直播流服务器")
|
||||
.setItems(history, (d, which) -> {
|
||||
String sel = history[which];
|
||||
if (TextUtils.isEmpty(sel)) return;
|
||||
StreamConfig.setStreamHostOverride(getApplicationContext(), sel);
|
||||
refresh();
|
||||
})
|
||||
.setNegativeButton("取消", null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.example.livestreaming.net;
|
||||
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.example.livestreaming.BuildConfig;
|
||||
|
||||
|
|
@ -18,9 +20,13 @@ import retrofit2.converter.gson.GsonConverterFactory;
|
|||
public final class ApiClient {
|
||||
|
||||
private static final String TAG = "ApiClient";
|
||||
private static final String PREFS_NAME = "api_client_prefs";
|
||||
private static final String KEY_BASE_URL_OVERRIDE = "base_url_override";
|
||||
private static final String KEY_BASE_URL_HISTORY = "base_url_history";
|
||||
private static volatile Retrofit retrofit;
|
||||
private static volatile ApiService service;
|
||||
private static volatile Context appContext;
|
||||
private static volatile String activeBaseUrl;
|
||||
|
||||
private ApiClient() {
|
||||
}
|
||||
|
|
@ -42,7 +48,7 @@ public final class ApiClient {
|
|||
/**
|
||||
* 获取API基础地址,自动根据设备类型选择
|
||||
*/
|
||||
private static String getBaseUrl() {
|
||||
private static String getAutoBaseUrl() {
|
||||
if (isEmulator()) {
|
||||
Log.d(TAG, "检测到模拟器,使用模拟器API地址");
|
||||
return BuildConfig.API_BASE_URL_EMULATOR;
|
||||
|
|
@ -52,6 +58,107 @@ public final class ApiClient {
|
|||
}
|
||||
}
|
||||
|
||||
private static String normalizeBaseUrl(String url) {
|
||||
String u = url != null ? url.trim() : "";
|
||||
if (u.isEmpty()) return "";
|
||||
if (!u.endsWith("/")) u = u + "/";
|
||||
return u;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getBaseUrlOverride(@Nullable Context context) {
|
||||
Context ctx = context != null ? context : appContext;
|
||||
if (ctx == null) return null;
|
||||
SharedPreferences sp = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
String v = sp.getString(KEY_BASE_URL_OVERRIDE, null);
|
||||
if (TextUtils.isEmpty(v)) return null;
|
||||
return normalizeBaseUrl(v);
|
||||
}
|
||||
|
||||
private static String resolveActiveBaseUrl(@Nullable Context context) {
|
||||
String override = getBaseUrlOverride(context);
|
||||
if (!TextUtils.isEmpty(override)) return override;
|
||||
return normalizeBaseUrl(getAutoBaseUrl());
|
||||
}
|
||||
|
||||
public static String getDefaultAutoBaseUrl(@Nullable Context context) {
|
||||
return normalizeBaseUrl(getAutoBaseUrl());
|
||||
}
|
||||
|
||||
public static String getCurrentBaseUrl(@Nullable Context context) {
|
||||
return resolveActiveBaseUrl(context);
|
||||
}
|
||||
|
||||
public static boolean isUsingCustomBaseUrl(@Nullable Context context) {
|
||||
return !TextUtils.isEmpty(getBaseUrlOverride(context));
|
||||
}
|
||||
|
||||
public static void setCustomBaseUrl(@Nullable Context context, String baseUrl) {
|
||||
Context ctx = context != null ? context.getApplicationContext() : appContext;
|
||||
if (ctx == null) return;
|
||||
String normalized = normalizeBaseUrl(baseUrl);
|
||||
if (TextUtils.isEmpty(normalized)) return;
|
||||
SharedPreferences sp = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
sp.edit().putString(KEY_BASE_URL_OVERRIDE, normalized).apply();
|
||||
addToHistory(ctx, normalized);
|
||||
reset();
|
||||
}
|
||||
|
||||
public static void clearCustomBaseUrl(@Nullable Context context) {
|
||||
Context ctx = context != null ? context.getApplicationContext() : appContext;
|
||||
if (ctx == null) return;
|
||||
SharedPreferences sp = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
sp.edit().remove(KEY_BASE_URL_OVERRIDE).apply();
|
||||
reset();
|
||||
}
|
||||
|
||||
public static String[] getBaseUrlHistory(@Nullable Context context) {
|
||||
Context ctx = context != null ? context.getApplicationContext() : appContext;
|
||||
if (ctx == null) return new String[0];
|
||||
SharedPreferences sp = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
String raw = sp.getString(KEY_BASE_URL_HISTORY, "");
|
||||
if (TextUtils.isEmpty(raw)) return new String[0];
|
||||
String[] lines = raw.split("\\n");
|
||||
java.util.List<String> out = new java.util.ArrayList<>();
|
||||
for (String s : lines) {
|
||||
String v = normalizeBaseUrl(s);
|
||||
if (TextUtils.isEmpty(v)) continue;
|
||||
if (!out.contains(v)) out.add(v);
|
||||
}
|
||||
return out.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static void addToHistory(Context context, String baseUrl) {
|
||||
if (context == null) return;
|
||||
String normalized = normalizeBaseUrl(baseUrl);
|
||||
if (TextUtils.isEmpty(normalized)) return;
|
||||
SharedPreferences sp = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
String raw = sp.getString(KEY_BASE_URL_HISTORY, "");
|
||||
java.util.LinkedHashSet<String> set = new java.util.LinkedHashSet<>();
|
||||
if (!TextUtils.isEmpty(raw)) {
|
||||
String[] lines = raw.split("\\n");
|
||||
for (String s : lines) {
|
||||
String v = normalizeBaseUrl(s);
|
||||
if (!TextUtils.isEmpty(v)) set.add(v);
|
||||
}
|
||||
}
|
||||
set.add(normalized);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String v : set) {
|
||||
if (sb.length() > 0) sb.append('\n');
|
||||
sb.append(v);
|
||||
}
|
||||
sp.edit().putString(KEY_BASE_URL_HISTORY, sb.toString()).apply();
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
synchronized (ApiClient.class) {
|
||||
retrofit = null;
|
||||
service = null;
|
||||
activeBaseUrl = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ApiService getService() {
|
||||
return getService(null);
|
||||
}
|
||||
|
|
@ -60,9 +167,15 @@ public final class ApiClient {
|
|||
if (context != null) {
|
||||
appContext = context.getApplicationContext();
|
||||
}
|
||||
if (service != null) return service;
|
||||
String desiredBaseUrl = resolveActiveBaseUrl(appContext);
|
||||
if (service != null && !TextUtils.isEmpty(activeBaseUrl) && TextUtils.equals(activeBaseUrl, desiredBaseUrl)) {
|
||||
return service;
|
||||
}
|
||||
synchronized (ApiClient.class) {
|
||||
if (service != null) return service;
|
||||
desiredBaseUrl = resolveActiveBaseUrl(appContext);
|
||||
if (service != null && !TextUtils.isEmpty(activeBaseUrl) && TextUtils.equals(activeBaseUrl, desiredBaseUrl)) {
|
||||
return service;
|
||||
}
|
||||
|
||||
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
|
||||
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
|
||||
|
|
@ -86,14 +199,14 @@ public final class ApiClient {
|
|||
.readTimeout(15, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.writeTimeout(15, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(true);
|
||||
|
||||
|
||||
OkHttpClient client = clientBuilder.build();
|
||||
|
||||
String baseUrl = getBaseUrl();
|
||||
Log.d(TAG, "API Base URL: " + baseUrl);
|
||||
activeBaseUrl = desiredBaseUrl;
|
||||
Log.d(TAG, "API Base URL: " + activeBaseUrl);
|
||||
|
||||
retrofit = new Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.baseUrl(activeBaseUrl)
|
||||
.client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
|
|
|
|||
|
|
@ -13,15 +13,15 @@ public interface ApiService {
|
|||
@POST("api/front/login")
|
||||
Call<ApiResponse<LoginResponse>> login(@Body LoginRequest body);
|
||||
|
||||
@GET("api/front/live/public/rooms")
|
||||
@GET("api/rooms")
|
||||
Call<ApiResponse<List<Room>>> getRooms();
|
||||
|
||||
@POST("api/front/live/rooms")
|
||||
@POST("api/rooms")
|
||||
Call<ApiResponse<Room>> createRoom(@Body CreateRoomRequest body);
|
||||
|
||||
@GET("api/front/live/public/rooms/{id}")
|
||||
@GET("api/rooms/{id}")
|
||||
Call<ApiResponse<Room>> getRoom(@Path("id") String id);
|
||||
|
||||
@DELETE("api/front/live/rooms/{id}")
|
||||
@DELETE("api/rooms/{id}")
|
||||
Call<ApiResponse<Object>> deleteRoom(@Path("id") String id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,145 @@
|
|||
package com.example.livestreaming.net;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
public final class StreamConfig {
|
||||
|
||||
private static final String PREFS_NAME = "stream_config_prefs";
|
||||
private static final String KEY_STREAM_HOST_OVERRIDE = "stream_host_override";
|
||||
private static final String KEY_STREAM_HOST_HISTORY = "stream_host_history";
|
||||
|
||||
private StreamConfig() {
|
||||
}
|
||||
|
||||
private static String normalizeHostPort(String input) {
|
||||
String v = input != null ? input.trim() : "";
|
||||
if (v.isEmpty()) return "";
|
||||
int schemeIdx = v.indexOf("://");
|
||||
if (schemeIdx >= 0) v = v.substring(schemeIdx + 3);
|
||||
int slashIdx = v.indexOf('/');
|
||||
if (slashIdx >= 0) v = v.substring(0, slashIdx);
|
||||
return v.trim();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getStreamHostOverride(@Nullable Context context) {
|
||||
if (context == null) return null;
|
||||
SharedPreferences sp = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
String v = sp.getString(KEY_STREAM_HOST_OVERRIDE, null);
|
||||
v = normalizeHostPort(v);
|
||||
if (TextUtils.isEmpty(v)) return null;
|
||||
return v;
|
||||
}
|
||||
|
||||
public static void setStreamHostOverride(@Nullable Context context, String hostOrHostPort) {
|
||||
if (context == null) return;
|
||||
String v = normalizeHostPort(hostOrHostPort);
|
||||
if (TextUtils.isEmpty(v)) return;
|
||||
SharedPreferences sp = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
sp.edit().putString(KEY_STREAM_HOST_OVERRIDE, v).apply();
|
||||
addToHistory(context, v);
|
||||
}
|
||||
|
||||
public static void clearStreamHostOverride(@Nullable Context context) {
|
||||
if (context == null) return;
|
||||
SharedPreferences sp = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
sp.edit().remove(KEY_STREAM_HOST_OVERRIDE).apply();
|
||||
}
|
||||
|
||||
public static String[] getStreamHostHistory(@Nullable Context context) {
|
||||
if (context == null) return new String[0];
|
||||
SharedPreferences sp = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
String raw = sp.getString(KEY_STREAM_HOST_HISTORY, "");
|
||||
if (TextUtils.isEmpty(raw)) return new String[0];
|
||||
String[] lines = raw.split("\\n");
|
||||
List<String> out = new ArrayList<>();
|
||||
for (String s : lines) {
|
||||
String v = normalizeHostPort(s);
|
||||
if (TextUtils.isEmpty(v)) continue;
|
||||
if (!out.contains(v)) out.add(v);
|
||||
}
|
||||
return out.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static void addToHistory(Context context, String hostPort) {
|
||||
if (context == null) return;
|
||||
String normalized = normalizeHostPort(hostPort);
|
||||
if (TextUtils.isEmpty(normalized)) return;
|
||||
SharedPreferences sp = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
String raw = sp.getString(KEY_STREAM_HOST_HISTORY, "");
|
||||
LinkedHashSet<String> set = new LinkedHashSet<>();
|
||||
if (!TextUtils.isEmpty(raw)) {
|
||||
String[] lines = raw.split("\\n");
|
||||
for (String s : lines) {
|
||||
String v = normalizeHostPort(s);
|
||||
if (!TextUtils.isEmpty(v)) set.add(v);
|
||||
}
|
||||
}
|
||||
set.add(normalized);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String v : set) {
|
||||
if (sb.length() > 0) sb.append('\n');
|
||||
sb.append(v);
|
||||
}
|
||||
sp.edit().putString(KEY_STREAM_HOST_HISTORY, sb.toString()).apply();
|
||||
}
|
||||
|
||||
public static String rewriteStreamUrl(@Nullable Context context, @Nullable String originalUrl) {
|
||||
if (context == null) return originalUrl;
|
||||
if (TextUtils.isEmpty(originalUrl)) return originalUrl;
|
||||
String override = getStreamHostOverride(context);
|
||||
if (TextUtils.isEmpty(override)) return originalUrl;
|
||||
|
||||
try {
|
||||
Uri u = Uri.parse(originalUrl);
|
||||
String scheme = u.getScheme();
|
||||
if (TextUtils.isEmpty(scheme)) return originalUrl;
|
||||
|
||||
String host = u.getHost();
|
||||
if (TextUtils.isEmpty(host)) {
|
||||
return originalUrl;
|
||||
}
|
||||
|
||||
String overrideHost = override;
|
||||
int overridePort = -1;
|
||||
int colonIdx = override.lastIndexOf(':');
|
||||
if (colonIdx > 0 && colonIdx < override.length() - 1) {
|
||||
String maybePort = override.substring(colonIdx + 1);
|
||||
String maybeHost = override.substring(0, colonIdx);
|
||||
if (!TextUtils.isEmpty(maybeHost) && maybePort.matches("\\d+")) {
|
||||
try {
|
||||
overridePort = Integer.parseInt(maybePort);
|
||||
overrideHost = maybeHost;
|
||||
} catch (Exception ignored) {
|
||||
overridePort = -1;
|
||||
overrideHost = override;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Uri.Builder b = u.buildUpon();
|
||||
if (overridePort >= 0) {
|
||||
b.authority(overrideHost + ":" + overridePort);
|
||||
} else {
|
||||
int p = u.getPort();
|
||||
if (p >= 0) {
|
||||
b.authority(overrideHost + ":" + p);
|
||||
} else {
|
||||
b.authority(overrideHost);
|
||||
}
|
||||
}
|
||||
return b.build().toString();
|
||||
} catch (Exception ignored) {
|
||||
return originalUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
## This file must *NOT* be checked into Version Control Systems,
|
||||
# as it contains information specific to your local configuration.
|
||||
#
|
||||
# Location of the SDK. This is only used by Gradle.
|
||||
# For customization when using a Version Control System, please read the
|
||||
# header note.
|
||||
#Tue Dec 16 09:00:29 CST 2025
|
||||
sdk.dir=C\:\\Users\\Administrator\\AppData\\Local\\Android\\Sdk
|
||||
Loading…
Reference in New Issue
Block a user