Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

P2P Media loader component #2

Merged
merged 43 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
ea53b46
Add JavaScript channels for peer connection and chunk download events
DimaDemchenko Jul 23, 2024
4793cc8
Test onPeerConnected event
DimaDemchenko Jul 23, 2024
f3b8684
Add event listeners for peer connection and chunk download events
DimaDemchenko Jul 23, 2024
424d3ab
Refactor VidstackPlayer component and add event listeners for peer co…
DimaDemchenko Jul 23, 2024
943e6aa
Refactor type parsing from JSON
DimaDemchenko Jul 23, 2024
d1f0ff6
Small improvements
DimaDemchenko Jul 23, 2024
46aa7ca
Improve error handling in VidstackPlayer component
DimaDemchenko Jul 23, 2024
36bf4e9
Add P2P download and upload statistics to VidstackPlayer
DimaDemchenko Jul 24, 2024
5af58c4
Refactor VidstackPlayer component and add P2P download and upload sta…
DimaDemchenko Jul 24, 2024
0c513ec
Update JavaScript channel name for peer connection event
DimaDemchenko Jul 25, 2024
79cf171
Add P2P functionality to VidstackPlayer component
DimaDemchenko Jul 26, 2024
0672b91
Refactor js p2p script in vidstack_player.html
DimaDemchenko Jul 26, 2024
e5d1733
Stylesheet moved upper
DimaDemchenko Jul 26, 2024
7ffb5ac
Clear statsInterval when change p2p status
DimaDemchenko Jul 26, 2024
fa51e62
Split logic between separate components
DimaDemchenko Jul 26, 2024
172d125
Refactor DemoView component and remove unused imports
DimaDemchenko Jul 26, 2024
2af8cdb
Refactor P2PStats class and related components
DimaDemchenko Aug 1, 2024
9028a45
Refactor structure
DimaDemchenko Aug 1, 2024
dd73ba8
Refactor P2PStatsManager and related components
DimaDemchenko Aug 1, 2024
e1c1435
Flutter assets
DimaDemchenko Aug 1, 2024
da00cba
Remove p2p stats class
DimaDemchenko Aug 1, 2024
ea30ff5
Merge pull request #3 from Novage/dev-fix-ios
DimaDemchenko Aug 1, 2024
e5f58e5
Update Vidstack player initialization
DimaDemchenko Aug 2, 2024
18ad00b
Update Vidstack player initialization
DimaDemchenko Aug 2, 2024
99834de
Remove unused code
DimaDemchenko Aug 2, 2024
c981d2b
Migration from webview_flutter to flutter_inappwebview
DimaDemchenko Aug 2, 2024
218cc38
Clean up Vidstack player initialization code
DimaDemchenko Aug 2, 2024
1e228e3
Small improvements
DimaDemchenko Aug 6, 2024
fe99453
Update Vidstack player lifecycle handling
DimaDemchenko Aug 7, 2024
1d90195
Remove widget test file
DimaDemchenko Aug 7, 2024
2690af0
Remove app state observer
DimaDemchenko Aug 8, 2024
26c8088
Remove redundant styles
DimaDemchenko Aug 8, 2024
7053834
Add title to demo screen
DimaDemchenko Aug 9, 2024
2858dc7
Update Vidstack player lifecycle handling
DimaDemchenko Aug 9, 2024
5ee03e0
Add test build workflow
DimaDemchenko Aug 12, 2024
7e347d9
Update build workflow to include iOS and Android builds
DimaDemchenko Aug 12, 2024
7f87a65
Update build workflow to include iOS and Android builds
DimaDemchenko Aug 12, 2024
49cfce5
Update build.yml
DimaDemchenko Aug 12, 2024
f98a4b1
Update Java and Gradle versions for build workflow
DimaDemchenko Aug 12, 2024
527d359
Update build workflow to use Ubuntu and OpenJDK 17
DimaDemchenko Aug 13, 2024
be0d8ff
Update build workflow to use Oracle JDK 17 and Flutter stable version
DimaDemchenko Aug 13, 2024
a7692de
Update build workflow to use Flutter stable version
DimaDemchenko Aug 13, 2024
215f3b6
Update Android Gradle plugin version to 7.4.2
DimaDemchenko Aug 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: "Build apk"

on:
pull_request:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set Up Java
uses: actions/setup-java@v4
with:
distribution: "oracle"
java-version: "17"

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"

- name: Get dependencies
run: flutter pub get

- name: Build Android
run: flutter build apk --release
27 changes: 0 additions & 27 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,28 +1 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.

# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml

linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
8 changes: 6 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ android {
ndkVersion = flutter.ndkVersion

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.toVersion("17")
targetCompatibility = JavaVersion.toVersion("17")
}

kotlinOptions {
jvmTarget = "17"
}

defaultConfig {
Expand Down
2 changes: 1 addition & 1 deletion android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pluginManagement {

plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "com.android.application" version "7.4.2" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}

Expand Down
227 changes: 191 additions & 36 deletions assets/vidstack_player.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,224 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<link rel="stylesheet" href="https://cdn.vidstack.io/player/theme.css" />
<link rel="stylesheet" href="https://cdn.vidstack.io/player/video.css" />

<script src="https://cdn.jsdelivr.net/npm/hls.js@~1/dist/hls.min.js"></script>
<script type="importmap">
{
"imports": {
"vidstack-player": "https://cdn.vidstack.io/player",
"p2p-media-loader-core": "https://cdn.jsdelivr.net/npm/p2p-media-loader-core@^1/dist/p2p-media-loader-core.es.min.js",
"p2p-media-loader-hlsjs": "https://cdn.jsdelivr.net/npm/p2p-media-loader-hlsjs@^1/dist/p2p-media-loader-hlsjs.es.min.js"
}
}
</script>

<link rel="stylesheet" href="https://cdn.vidstack.io/player/theme.css" />
<link rel="stylesheet" href="https://cdn.vidstack.io/player/video.css" />
<script type="module">
import { VidstackPlayer, VidstackPlayerLayout } from "vidstack-player";
import { HlsJsP2PEngine } from "p2p-media-loader-hlsjs";

<script src="https://cdn.vidstack.io/player" type="module"></script>
class P2P {
constructor() {
this.hlsP2PEngine = null;
this.stats = {
httpDownloadedBytes: 0,
p2pDownloadedBytes: 0,
p2pUploadedBytes: 0,
};

<style>
html,
body {
margin: 0;
padding: 0;
width: 100vw;
background-color: transparent;
}
</style>
this.init();
}

<script type="module">
import { HlsJsP2PEngine } from "p2p-media-loader-hlsjs";
init = async () => {
this.player = await VidstackPlayer.create({
target: "#target",
src: "https://cph-p2p-msl.akamaized.net//hls/live/2000341/test/master.m3u8",
layout: new VidstackPlayerLayout(),
playsInline: true,
});

const player = document.querySelector("media-player");
const HlsWithP2P = HlsJsP2PEngine.injectMixin(window.Hls);
this.setupEventListeners();
this.startStatsInterval();
};

player.addEventListener("provider-change", (event) => {
const provider = event.detail;
setupEventListeners = () => {
this.player.addEventListener(
"provider-change",
this.onProviderChange
);

if (provider?.type === "hls") {
provider.library = HlsWithP2P;
this.player.addEventListener("play", () => {
updateP2PState(false);
});

this.player.addEventListener("pause", () => {
updateP2PState(true);
});
};

onProviderChange = (event) => {
const provider = event.detail;
if (provider?.type === "hls") {
this.setupHlsProvider(provider);
}
};

setupHlsProvider = (provider) => {
const HlsWithP2P = HlsJsP2PEngine.injectMixin(window.Hls);
provider.library = HlsWithP2P;
provider.config = {
p2p: {
core: {
swarmId: "Optional custom swarm ID for stream",
},
onHlsJsCreated: (hls) => {
hls.p2pEngine.addEventListener("onPeerConnect", (params) => {
console.log("Peer connected:", params.peerId);
});
isP2PDisabled: true,
},
onHlsJsCreated: this.onHlsJsCreated,
},
};
}
};

onHlsJsCreated = (hls) => {
this.hlsP2PEngine = hls.p2pEngine;
this.setupP2PEventListeners(hls.p2pEngine);
};

setupP2PEventListeners = (p2pEngine) => {
p2pEngine.addEventListener("onPeerConnect", this.onPeerConnect);
p2pEngine.addEventListener("onPeerClose", this.onPeerClose);
p2pEngine.addEventListener(
"onChunkDownloaded",
this.onChunkDownloaded
);
p2pEngine.addEventListener("onChunkUploaded", this.onChunkUploaded);
};

onPeerConnect = (params) => {
window.flutter_inappwebview?.callHandler("onPeerConnect", params);
};

onPeerClose = (params) => {
window.flutter_inappwebview?.callHandler("onPeerClose", params);
};

onChunkDownloaded = (bytesLength, downloadSource) => {
if (downloadSource === "http") {
this.stats.httpDownloadedBytes += bytesLength;
} else if (downloadSource === "p2p") {
this.stats.p2pDownloadedBytes += bytesLength;
}
};

onChunkUploaded = (bytesLength) => {
this.stats.p2pUploadedBytes += bytesLength;
};

startStatsInterval = () => {
this.statsInterval = setInterval(this.postStats, 1000);
};

stopStatsInterval = () => {
clearInterval(this.statsInterval);
this.statsInterval = undefined;
};

postStats = () => {
if (this.stats.p2pUploadedBytes > 0) {
this.postUploadStats(this.stats.p2pUploadedBytes);
this.stats.p2pUploadedBytes = 0;
}

if (this.stats.httpDownloadedBytes > 0) {
this.postDownloadStats(this.stats.httpDownloadedBytes, "http");
this.stats.httpDownloadedBytes = 0;
}

if (this.stats.p2pDownloadedBytes > 0) {
this.postDownloadStats(this.stats.p2pDownloadedBytes, "p2p");
this.stats.p2pDownloadedBytes = 0;
}
};

postDownloadStats = (downloadedBytes, downloadSource) => {
window.flutter_inappwebview?.callHandler(
"onChunkDownloaded",
downloadedBytes,
downloadSource
);
};

postUploadStats = (uploadedBytes) => {
window.flutter_inappwebview?.callHandler(
"onChunkUploaded",
uploadedBytes
);
};

destroyP2P = () => {
this.stopStatsInterval();
if (!this.hlsP2PEngine) return;

this.hlsP2PEngine.removeEventListener(
"onPeerConnect",
this.onPeerConnect
);
this.hlsP2PEngine.removeEventListener(
"onPeerClose",
this.onPeerClose
);
this.hlsP2PEngine.removeEventListener(
"onChunkDownloaded",
this.onChunkDownloaded
);
this.hlsP2PEngine.removeEventListener(
"onChunkUploaded",
this.onChunkUploaded
);

this.hlsP2PEngine.destroy();
this.hlsP2PEngine = undefined;
};

updateP2PState = (isDisabled) => {
if (!this.hlsP2PEngine) return;
const currentState =
this.hlsP2PEngine.getConfig().core.mainStream.isP2PDisabled;

if (currentState === isDisabled) return;

this.hlsP2PEngine.applyDynamicConfig({
core: { isP2PDisabled: isDisabled },
});

if (isDisabled) {
this.stopStatsInterval();
} else {
this.startStatsInterval();
}
};
}

document.addEventListener("DOMContentLoaded", () => {
const p2p = new P2P();

window.updateP2PState = p2p.updateP2PState;

window.addEventListener("unload", () => {
p2p.destroyP2P();
});
});
</script>

<style>
html,
body {
margin: 0;
padding: 0;
}
</style>
</head>

<body>
<div>
<media-player
playsInline
src="https://cph-p2p-msl.akamaized.net/hls/live/2000341/test/master.m3u8"
>
<media-provider></media-provider>
<media-video-layout></media-video-layout>
</media-player>
</div>
<div id="target"></div>
</body>
</html>
Loading