├── .gitignore ├── release.zip ├── images ├── player128.png ├── player16.png ├── player32.png └── player48.png ├── dist ├── images │ ├── player128.png │ ├── player16.png │ ├── player32.png │ └── player48.png ├── constants.js.map ├── manifest.json ├── popup.html ├── background.js.map ├── constants.js ├── background.js ├── content.js ├── content.js.map └── popup.94081321.js ├── README.md ├── .parcelrc ├── src ├── constants.ts ├── background.ts ├── popup.tsx ├── popup.html └── content.ts ├── manifest.json ├── static └── manifest.json ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .cache/ -------------------------------------------------------------------------------- /release.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LookRain/pip-player-chrome-ext/HEAD/release.zip -------------------------------------------------------------------------------- /images/player128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LookRain/pip-player-chrome-ext/HEAD/images/player128.png -------------------------------------------------------------------------------- /images/player16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LookRain/pip-player-chrome-ext/HEAD/images/player16.png -------------------------------------------------------------------------------- /images/player32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LookRain/pip-player-chrome-ext/HEAD/images/player32.png -------------------------------------------------------------------------------- /images/player48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LookRain/pip-player-chrome-ext/HEAD/images/player48.png -------------------------------------------------------------------------------- /dist/images/player128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LookRain/pip-player-chrome-ext/HEAD/dist/images/player128.png -------------------------------------------------------------------------------- /dist/images/player16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LookRain/pip-player-chrome-ext/HEAD/dist/images/player16.png -------------------------------------------------------------------------------- /dist/images/player32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LookRain/pip-player-chrome-ext/HEAD/dist/images/player32.png -------------------------------------------------------------------------------- /dist/images/player48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LookRain/pip-player-chrome-ext/HEAD/dist/images/player48.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pip-player-chrome-ext 2 | Chrome extension that allows you to play video in picture-in-picture mode 3 | https://chrome.google.com/webstore/detail/%E6%82%AC%E6%B5%AE%E7%94%BB%E4%B8%AD%E7%94%BB%E6%92%AD%E6%94%BE%E5%99%A8/gdcfkpenohoihodlddbcgpdjhmdjepnb 4 | -------------------------------------------------------------------------------- /.parcelrc: -------------------------------------------------------------------------------- 1 | { 2 | "optimizers": { 3 | "*.js": ["@parcel/optimizer-uglify"], 4 | "*.css": ["@parcel/optimizer-cssnano"], 5 | "*.html": ["@parcel/optimizer-htmlnano"], 6 | "*.{png,jpg,jpeg,svg,...}": ["@parcel/optimizer-imagemin"] 7 | }, 8 | "reporters": ["@parcel/reporter-cli"] 9 | } -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const stateMachine = { 2 | idle: { 3 | PLAY: 'attemptingToPlay', 4 | }, 5 | attemptingToPlay: { 6 | SUCCESS: 'playing', 7 | FAIL: 'error', 8 | }, 9 | playing: { 10 | QUIT: 'idle', 11 | }, 12 | error: { 13 | PLAY: 'attemptingToPlay', 14 | }, 15 | }; 16 | 17 | export type Commands = 18 | | 'play' 19 | | 'original-player-play' 20 | | 'play-next' 21 | | 'mute' 22 | | {continuous: boolean} 23 | | {multiVideo: boolean}; 24 | -------------------------------------------------------------------------------- /dist/constants.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["constants.ts"],"names":[],"mappings":";AAAa,aAAA,OAAA,eAAA,QAAA,aAAA,CAAA,OAAA,IAAA,QAAA,aAAe,CAC1B,KAAM,CACJ,KAAM,oBAER,iBAAkB,CAChB,QAAS,UACT,KAAM,SAER,QAAS,CACP,KAAM,QAER,MAAO,CACL,KAAM","file":"constants.js","sourceRoot":"..\\src","sourcesContent":["export const stateMachine = {\r\n idle: {\r\n PLAY: 'attemptingToPlay',\r\n },\r\n attemptingToPlay: {\r\n SUCCESS: 'playing',\r\n FAIL: 'error',\r\n },\r\n playing: {\r\n QUIT: 'idle',\r\n },\r\n error: {\r\n PLAY: 'attemptingToPlay',\r\n },\r\n};\r\n\r\nexport type Commands =\r\n | 'play'\r\n | 'original-player-play'\r\n | 'play-next'\r\n | 'mute'\r\n | {continuous: boolean}\r\n | {multiVideo: boolean};\r\n"]} -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "悬浮画中画播放器", 3 | "version": "2.4", 4 | "description": "使用chrome提供的picture-in-picture api让直播以画中画模式播放。 目前支持斗鱼,虎牙,火猫直播以及各种使用html5播放器的网站。", 5 | "permissions": ["activeTab"], 6 | "background": { 7 | "scripts": ["background.js"], 8 | "persistent": false 9 | }, 10 | "browser_action": { 11 | "default_popup": "popup.html", 12 | "default_icon": { 13 | "16": "images/player16.png", 14 | "32": "images/player32.png", 15 | "48": "images/player48.png", 16 | "128": "images/player128.png" 17 | } 18 | }, 19 | "icons": { 20 | "16": "images/player16.png", 21 | "32": "images/player32.png", 22 | "48": "images/player48.png", 23 | "128": "images/player128.png" 24 | }, 25 | "manifest_version": 2 26 | } 27 | -------------------------------------------------------------------------------- /dist/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "悬浮画中画播放器", 3 | "version": "2.4", 4 | "description": "使用chrome提供的picture-in-picture api让直播以画中画模式播放。 目前支持斗鱼,虎牙,火猫直播以及各种使用html5播放器的网站。", 5 | "permissions": ["activeTab"], 6 | "background": { 7 | "scripts": ["background.js"], 8 | "persistent": false 9 | }, 10 | "browser_action": { 11 | "default_popup": "popup.html", 12 | "default_icon": { 13 | "16": "images/player16.png", 14 | "32": "images/player32.png", 15 | "48": "images/player48.png", 16 | "128": "images/player128.png" 17 | } 18 | }, 19 | "icons": { 20 | "16": "images/player16.png", 21 | "32": "images/player32.png", 22 | "48": "images/player48.png", 23 | "128": "images/player128.png" 24 | }, 25 | "manifest_version": 2 26 | } 27 | -------------------------------------------------------------------------------- /src/background.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | 'use strict'; 6 | const paths = ['com']; 7 | const genConditions = (list) => { 8 | return list.map((word) => { 9 | return new chrome.declarativeContent.PageStateMatcher({ 10 | pageUrl: {hostContains: word}, 11 | }) 12 | }); 13 | } 14 | chrome.runtime.onInstalled.addListener(function() { 15 | chrome.declarativeContent.onPageChanged.removeRules(undefined, function() { 16 | chrome.declarativeContent.onPageChanged.addRules([{ 17 | conditions: genConditions(paths), 18 | actions: [new chrome.declarativeContent.ShowPageAction()] 19 | }]); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "悬浮画中画播放器", 3 | "version": "2.4", 4 | "description": "使用chrome提供的picture-in-picture api让直播以画中画模式播放。 目前支持斗鱼,虎牙,火猫直播以及各种使用html5播放器的网站。", 5 | "permissions": ["activeTab"], 6 | "background": { 7 | "scripts": ["background.js"], 8 | "persistent": false 9 | }, 10 | "browser_action": { 11 | "default_popup": "popup.html", 12 | "default_icon": { 13 | "16": "images/player16.png", 14 | "32": "images/player32.png", 15 | "48": "images/player48.png", 16 | "128": "images/player128.png" 17 | } 18 | }, 19 | "icons": { 20 | "16": "images/player16.png", 21 | "32": "images/player32.png", 22 | "48": "images/player48.png", 23 | "128": "images/player128.png" 24 | }, 25 | "manifest_version": 2 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stream-pip", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "https://github.com/LookRain/streaming-pip.git", 6 | "author": "Lu Yu (Louis) ", 7 | "license": "MIT", 8 | "scripts": { 9 | "clean": "rm -rf dist && rm release.zip", 10 | "release": "cp static/* dist && cp -r images dist && zip -r release.zip dist", 11 | "build": "yarn clean && parcel build src/popup.html src/*.ts && yarn release", 12 | "build-only": "parcel build src/popup.html src/*.ts && cp static/* dist && cp -r images dist", 13 | "dev": "parcel watch build src/popup.html src/*.ts && yarn release" 14 | }, 15 | "devDependencies": { 16 | "@types/chrome": "^0.0.91", 17 | "@types/react": "^16.9.17", 18 | "@types/react-dom": "^16.9.4", 19 | "cssnano": "^4.1.10", 20 | "parcel": "^1.12.4", 21 | "typescript": "^3.7.4" 22 | }, 23 | "dependencies": { 24 | "react": "^16.12.0", 25 | "react-dom": "^16.12.0" 26 | } 27 | } -------------------------------------------------------------------------------- /dist/popup.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /dist/background.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["background.ts"],"names":[],"mappings":";AAIA,aACA,IAAM,EAAQ,CAAC,OACT,EAAgB,SAAC,GACd,OAAA,EAAK,IAAI,SAAC,GACR,OAAA,IAAI,OAAO,mBAAmB,iBAAiB,CACpD,QAAS,CAAC,aAAc,QAI9B,OAAO,QAAQ,YAAY,YAAY,WACrC,OAAO,mBAAmB,cAAc,iBAAY,EAAW,WAC7D,OAAO,mBAAmB,cAAc,SAAS,CAAC,CAChD,WAAY,EAAc,GAC1B,QAAS,CAAC,IAAI,OAAO,mBAAmB","file":"background.js","sourceRoot":"..\\src","sourcesContent":["// Copyright 2018 The Chromium Authors. All rights reserved.\r\n// Use of this source code is governed by a BSD-style license that can be\r\n// found in the LICENSE file.\r\n\r\n'use strict';\r\nconst paths = ['com'];\r\nconst genConditions = (list) => {\r\n return list.map((word) => {\r\n return new chrome.declarativeContent.PageStateMatcher({\r\n pageUrl: {hostContains: word},\r\n })\r\n });\r\n}\r\nchrome.runtime.onInstalled.addListener(function() {\r\n chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {\r\n chrome.declarativeContent.onPageChanged.addRules([{\r\n conditions: genConditions(paths),\r\n actions: [new chrome.declarativeContent.ShowPageAction()]\r\n }]);\r\n });\r\n});\r\n"]} -------------------------------------------------------------------------------- /dist/constants.js: -------------------------------------------------------------------------------- 1 | parcelRequire=function(e,r,t,n){var i,o="function"==typeof parcelRequire&&parcelRequire,u="function"==typeof require&&require;function f(t,n){if(!r[t]){if(!e[t]){var i="function"==typeof parcelRequire&&parcelRequire;if(!n&&i)return i(t,!0);if(o)return o(t,!0);if(u&&"string"==typeof t)return u(t);var c=new Error("Cannot find module '"+t+"'");throw c.code="MODULE_NOT_FOUND",c}p.resolve=function(r){return e[t][1][r]||r},p.cache={};var l=r[t]=new f.Module(t);e[t][0].call(l.exports,p,l,l.exports,this)}return r[t].exports;function p(e){return f(p.resolve(e))}}f.isParcelRequire=!0,f.Module=function(e){this.id=e,this.bundle=f,this.exports={}},f.modules=e,f.cache=r,f.parent=o,f.register=function(r,t){e[r]=[function(e,r){r.exports=t},{}]};for(var c=0;c { 28 | const [state, setState] = React.useState("idle"); 29 | const [multiVideo, setMultiVideo] = React.useState(false); 30 | React.useEffect(() => { 31 | chrome.runtime.onMessage.addListener(function(request) { 32 | if (request.state) { 33 | setState(request.state); 34 | } 35 | }); 36 | 37 | chrome.runtime.onMessage.addListener(function(request) { 38 | if (request.multiVideo === true) { 39 | setMultiVideo(true); 40 | } 41 | }); 42 | }, []); 43 | return ( 44 |
49 | {state === "idle" && ( 50 | 53 | )} 54 | {state === "playing" && ( 55 | 58 | )} 59 | {multiVideo && ( 60 | <> 61 |
62 | 发现当前页面上有多于1个视频,如果当前播放的视频不正确,请尝试选择下一个视频 63 |
64 | 67 | 68 | )} 69 |
70 | ); 71 | }; 72 | 73 | ReactDOM.render(, document.getElementById("root")); 74 | -------------------------------------------------------------------------------- /src/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 94 | 95 | 96 | 97 |
98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /dist/content.js: -------------------------------------------------------------------------------- 1 | parcelRequire=function(e,r,t,n){var i,o="function"==typeof parcelRequire&&parcelRequire,u="function"==typeof require&&require;function f(t,n){if(!r[t]){if(!e[t]){var i="function"==typeof parcelRequire&&parcelRequire;if(!n&&i)return i(t,!0);if(o)return o(t,!0);if(u&&"string"==typeof t)return u(t);var c=new Error("Cannot find module '"+t+"'");throw c.code="MODULE_NOT_FOUND",c}p.resolve=function(r){return e[t][1][r]||r},p.cache={};var l=r[t]=new f.Module(t);e[t][0].call(l.exports,p,l,l.exports,this)}return r[t].exports;function p(e){return f(p.resolve(e))}}f.isParcelRequire=!0,f.Module=function(e){this.id=e,this.bundle=f,this.exports={}},f.modules=e,f.cache=r,f.parent=o,f.register=function(r,t){e[r]=[function(e,r){r.exports=t},{}]};for(var c=0;c0&&o[o.length-1])&&(6===i[0]||2===i[0])){u=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]1&&chrome.runtime.sendMessage({multiVideo:!0}),e}();return e?a(e):setTimeout(l,1e3),e}chrome.runtime.onMessage.addListener(function(n,r,i){var u,s=n.command;"play"===s&&l(),"original-player-play"===s&&function(){e(this,void 0,void 0,function(){return t(this,function(e){switch(e.label){case 0:return[4,document.exitPictureInPicture()];case 1:return e.sent(),o("QUIT"),[2]}})})}(),"play-next"===s&&(u=document.querySelectorAll("video").length,c>=u-1?c=0:c++,a(document.querySelectorAll("video")[c]))})}(); 5 | },{"./constants":"eKDL"}]},{},["hNRT"], null) 6 | //# sourceMappingURL=/content.js.map -------------------------------------------------------------------------------- /src/content.ts: -------------------------------------------------------------------------------- 1 | import { stateMachine, Commands } from "./constants"; 2 | 3 | (function() { 4 | interface HTMLVideoElementNew extends HTMLVideoElement { 5 | requestPictureInPicture: () => Promise; 6 | } 7 | 8 | let state: keyof typeof stateMachine = "idle"; 9 | const transition = ( 10 | action: "PLAY" | "SUCCESS" | "FAIL" | "QUIT" | "PLAY" 11 | ) => { 12 | const currentState = stateMachine[state]; 13 | if (action in currentState) { 14 | state = (currentState as any)[action]; 15 | } 16 | chrome.runtime.sendMessage({ state }); 17 | }; 18 | 19 | const reset = () => { 20 | state = "idle"; 21 | chrome.runtime.sendMessage({ state }); 22 | }; 23 | 24 | const siteToQuerySelectorMap = { 25 | douyu: '[id^="__video"]', 26 | huya: "#huya_video", 27 | huomao: "#live-video" 28 | }; 29 | 30 | let currentVideoIndex = 0; 31 | 32 | function getVideoEl() { 33 | const origin = document.location.origin; 34 | let video: HTMLVideoElementNew | null; 35 | 36 | const matchedSite = Object.keys( 37 | siteToQuerySelectorMap 38 | ).find(sitePartialName => origin.includes(sitePartialName)); 39 | 40 | if (matchedSite) { 41 | video = document.querySelector( 42 | siteToQuerySelectorMap[ 43 | matchedSite as keyof typeof siteToQuerySelectorMap 44 | ] 45 | ); 46 | } else { 47 | video = document.querySelector("video") as HTMLVideoElementNew; 48 | } 49 | 50 | // if there're more than 1