Skip to content

Commit

Permalink
Feat: rebuild douyu stream with js
Browse files Browse the repository at this point in the history
  • Loading branch information
Jackiu1997 committed Feb 4, 2023
1 parent ef49a50 commit 8136c2a
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 53 deletions.
6 changes: 5 additions & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ android {
applicationId "com.mystyle.purelive"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion
minSdkVersion 21 //flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand All @@ -72,6 +72,10 @@ android {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'),
'proguard-rules.pro'
}
debug {
signingConfig signingConfigs.release
Expand Down
8 changes: 8 additions & 0 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#Flutter Wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
-keep class de.prosiebensat1digital.** { *; }
144 changes: 92 additions & 52 deletions lib/common/api/platform/douyu.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter_js/flutter_js.dart';
import 'package:pure_live/common/index.dart';
import 'package:http/http.dart' as http;
import 'package:crypto/crypto.dart';

class DouyuApi {
static Future<dynamic> _getJson(String url) async {
Expand All @@ -16,50 +16,103 @@ class DouyuApi {
return await jsonDecode(resp.body);
}

static Future<Map?> getHomeJs(String rid) async {
String roomUrl = "https://www.douyu.com/" + rid;
String response = (await http.get(Uri.parse(roomUrl))).body;

String realRid = response.substring(
response.indexOf("\$ROOM.room_id =") + ("\$ROOM.room_id =").length);
realRid = realRid.substring(0, realRid.indexOf(";")).trim();
if (rid != realRid) {
roomUrl = "https://www.douyu.com/" + realRid;
response = (await http.get(Uri.parse(roomUrl))).body;
}

final pattern = RegExp(
"(vdwdae325w_64we[\\s\\S]*function ub98484234[\\s\\S]*?)function");
final matcher = pattern.allMatches(response);
if (matcher.isEmpty) return null;
String result = matcher.toList()[0][0]!;
String homejs = result.replaceAll("eval.*?;", "strc;");
return {
"homejs": homejs,
"real_rid": realRid,
};
}

static final jsEngine = getJavascriptRuntime();
static bool loadedJs = false;
static Future<String> getSign(String rid, String tt, String ub9) async {
// load crypto-js package
if (!loadedJs) {
final cryptojs = (await http.get(Uri.parse(
'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.js')))
.body;
jsEngine.evaluate(cryptojs);
loadedJs = true;
}

ub9 = ub9.substring(0, ub9.lastIndexOf('function'));
jsEngine.evaluate(ub9);
final params = jsEngine
.evaluate(
'ub98484234(\'$rid\', \'10000000000000000000000000001501\', \'$tt\')')
.toString();
return params;
}

static Map<String, String> handleParams(String params) {
Map<String, String> paramsMap = {};
for (String param in params.split("&")) {
final arr1 = param.split("=");
final key = arr1[0].trim();
final value = arr1[1].trim();
paramsMap[key] = value;
}
return paramsMap;
}

static Future<Map<String, Map<String, String>>> getRoomStreamLink(
RoomInfo room) async {
Map<String, Map<String, String>> links = {};

String url =
'https://playweb.douyucdn.cn/lapi/live/hlsH5Preview/${room.roomId}';

final rid = room.roomId;
try {
String time = ((DateTime.now().millisecondsSinceEpoch) * 1000).toString();
String sign = md5.convert(utf8.encode('${room.roomId}$time')).toString();
Map<String, String> headers = {
'rid': room.roomId,
'time': time,
'auth': sign
};
Map data = {
'rid': room.roomId,
'did': '10000000000000000000000000001501'
};
var resp = await http.post(Uri.parse(url), headers: headers, body: data);
var body = json.decode(resp.body);
if (body['error'] == 0) {
String rtmpLive = body['data']['rtmp_live'];
RegExpMatch? match =
RegExp(r'(\d{1,8}[0-9a-zA-Z]+)_?\d{0,4}(/playlist|.m3u8)')
.firstMatch(rtmpLive);
String? key = match?.group(1);
// 获取房间主页JS
final result = await getHomeJs(rid);
final realRid = result!["real_rid"];
final homejs = result["homejs"];
// 执行JS获取签名信息
final tt = (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString();
final params = (await getSign(rid, tt, homejs)) + "&cdn=ws-h5&rate=0";

// add stream links
Map<String, String> resolutions = {
'原画': '',
'蓝光8M': '_2000',
'蓝光4M': '_1500',
'超清': '_1200',
'流畅': '_900',
};
List<String> cdns = ['hw', 'ws', 'akm'];
for (String res in resolutions.keys) {
String v = resolutions[res]!;
links[res] = {};
for (String cdn in cdns) {
links[res]![cdn] =
'https://$cdn-tct.douyucdn.cn/live/$key$v.flv?uuid=';
}
// 发送请求获取直播流信息
String requestUrl =
"https://www.douyu.com/lapi/live/getH5Play/" + realRid;
final response1 = await http.post(
Uri.parse(requestUrl),
body: handleParams(params),
);
final response = jsonDecode(response1.body);
final data = response["data"];

// 获取真实直播Key
final url = data["rtmp_live"];
final key = url.substring(0, url.indexOf(".")).split("_")[0];

// 获取支持分辨率
Map<String, String> resolutions = {};
data["multirates"].forEach((e) {
resolutions[e['name']] = e['rate'] == 0 ? '' : '_${e['bit']}';
});

List<String> cdns = ['akm', 'hw', 'ws'];
for (String res in resolutions.keys) {
String v = resolutions[res]!;
links[res] = {};
for (String cdn in cdns) {
links[res]![cdn] =
'https://$cdn-tct.douyucdn.cn/live/$key$v.flv?uuid=';
}
}
} catch (e) {
Expand All @@ -86,19 +139,6 @@ class DouyuApi {
? LiveStatus.live
: LiveStatus.offline;
}
// fix douyu replay status
try {
dynamic body = await _getJson(
'https://www.douyu.com/wgapi/live/liveweb/getRoomLoopInfo?rid=${room.roomId}');
if (body['error'] == 0) {
Map data = body['data'];
if (data.containsKey('rst') && data['rst'] == 3) {
room.liveStatus = LiveStatus.replay;
}
}
} catch (e) {
log(e.toString(), name: 'DouyuApi.getRoomInfo.rstinfo');
}
} catch (e) {
log(e.toString(), name: 'DouyuApi.getRoomInfo');
return room;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ class VideoController with ChangeNotifier {

void setDataSource(String url) {
datasource = url;
// fix datasource empty error
if (datasource.isEmpty) {
hasError.value = true;
return;
}

if (Platform.isWindows || Platform.isLinux) {
desktopController?.pause();
desktopController?.open(
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies:
sdk: flutter

# network
flutter_js: ^0.5.0
html: ^0.15.0
http: ^0.13.4
url_launcher: ^6.1.5
Expand Down
3 changes: 3 additions & 0 deletions windows/flutter/generated_plugin_registrant.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <battery_plus/battery_plus_windows_plugin.h>
#include <dart_vlc/dart_vlc_plugin.h>
#include <dynamic_color/dynamic_color_plugin_c_api.h>
#include <flutter_js/flutter_js_plugin.h>
#include <flutter_native_view/flutter_native_view_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <screen_brightness_windows/screen_brightness_windows_plugin.h>
Expand All @@ -23,6 +24,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("DartVlcPlugin"));
DynamicColorPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
FlutterJsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterJsPlugin"));
FlutterNativeViewPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterNativeViewPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
Expand Down
1 change: 1 addition & 0 deletions windows/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
battery_plus
dart_vlc
dynamic_color
flutter_js
flutter_native_view
permission_handler_windows
screen_brightness_windows
Expand Down

0 comments on commit 8136c2a

Please sign in to comment.