您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds MultiStream Vod Watching and a Button to view logs in xivanalysis also some minor improvements.
当前为
- // ==UserScript==
- // @name FFProgs
- // @name:en FFProgs
- // @name:ja FFプログレス
- // @namespace k_fizzel
- // @version 1.0.0
- // @author k_fizzel
- // @description Adds MultiStream Vod Watching and a Button to view logs in xivanalysis also some minor improvements.
- // @description:en Adds MultiStream VOD Watching and a Button to view logs in xivanalysis also some minor improvements.
- // @description:ja マルチストリームVODウォッチングとxivanalysisでログを表示するためのボタンもいくつかのマイナーな改善を追加します。
- // @website https://www.fflogs.com/character/id/12781922
- // @icon https://assets.rpglogs.com/img/ff/favicon.png?v=2
- // @match https://www.fflogs.com/*
- // @match https://*.fflogs.com/*
- // @grant GM_addStyle
- // @grant unsafeWindow
- // @license MIT License
- // ==/UserScript==
- /*
- To Do:
- refactor code to make it look better pick a standard and use es6 functions and jquery
- right click a log in the encounters page to highlight with a specific color it so that it stands out
- pause video when it gets to the end of a replay
- have global event listeners instead of a bunch of click ones
- make the video player stay in the same x y on log pull change
- Export current vod data so that you can import it
- when you share a twitch/youtube url make the timestamp the start
- make offset work with decimals
- Make youtube player work and private youtube streams as well
- github once I get to 1.0.0 with a readme on how to install delete this current git repo
- Maybe:
- add keybindings
- make the player keep the same aspect ratio
- make player start at a larger size
- greasy fork pull from github for updates
- */
- (function () {
- // Helper functions
- function addGlobalEventListener(type, selector, callaBack) {
- document.addEventListener(type, e => {
- if (e.target.matches(selector)) callaBack(e);
- })
- }
- // Adblock.
- $("#top-banner, #bottom-banner, #playwire-video-container, #patron-box, #gear-box-ad").remove();
- // Remove alt-text from item images. (Alt text looks awful when api is not up to date and relics )
- $(".table-icon").removeAttr("alt");
- // Adds xivanalysis button.
- const updateXIVAnalysisUrl = () => { $("#xivanalysis-tab").attr("href", `https://xivanalysis.com/report-redirect/${location.href}`); }
- $("#filter-analyze-tab").before(`<a href="https://xivanalysis.com/report-redirect/${location.href}" target="_blank" class="big-tab view-type-tab" id="xivanalysis-tab"><span class="zmdi zmdi-time-interval"></span> <span class="big-tab-text"><br>xivanalysis</span></a>`)
- $("#xivanalysis-tab").click(updateXIVAnalysisUrl);
- // Checks if video player button exists
- const videoButton = document.querySelector(".replay-video");
- if (/\/reports\/.+/.test(location.pathname)) {
- const streams = {
- // test data
- "Chad_Bradly": { platform: 1, url: 'https://www.twitch.tv/videos/787291919?t=01h04m19s', offset: -1312 },
- "Charlie_Cerise": { platform: 2, url: 'https://www.youtube.com/watch?v=d_BBgHPIlWg' },
- "Gale_Eternia": { platform: 2, url: 'https://www.youtube.com/watch?v=d_BBgHPIlWg' },
- "Glyphimor_Epsilon": { platform: 1, url: 'https://www.twitch.tv/videos/787291919?t=01h04m19s' },
- "Kara_Doomfist": { platform: 2, url: 'https://www.youtube.com/watch?v=d_BBgHPIlWg' },
- "M'aique_Delieur": { platform: 1, url: 'https://www.twitch.tv/videos/787291919?t=01h04m19s' },
- "Nishi_Michu": { platform: 2, url: 'https://www.youtube.com/watch?v=d_BBgHPIlWg' },
- "Pual_Pual": { platform: 1, url: 'https://www.twitch.tv/videos/787291919?t=01h04m19s' }
- }
- let person, videoFrame, videoPlayer, allowPlay = false, timesUpdate = 0, videPlayerOpen = false, multiStreamOpen = false, multiStreamMovement = false, isDragging = false;
- // iframe api's
- const firstScriptTag = $("script")[0]
- $(`<script src="https://player.twitch.tv/js/embed/v1.js"></script>
- <script src="https://www.youtube.com/iframe_api"></script>`).insertBefore(firstScriptTag);
- // doesn't work with jquery LMAO
- const interactModule = document.createElement("script");
- interactModule.type = "module";
- interactModule.innerHTML = `
- import interact from "https://cdn.interactjs.io/v1.10.11/interactjs/index.js";
- interact("#video-frame").resizable({
- edges: { left: true, right: true, bottom: true, top: true },
- listeners: {
- move (event) {
- let target = event.target;
- let x = (parseFloat(target.getAttribute("data-x")) || 0);
- let y = (parseFloat(target.getAttribute("data-y")) || 0);
- target.style.width = event.rect.width + "px";
- target.style.height = event.rect.height + "px";
- x += event.deltaRect.left;
- y += event.deltaRect.top;
- target.style.transform = \`translate(\${x}px, \${y}px)\`;
- target.setAttribute("data-x", x);
- target.setAttribute("data-y", y);
- }
- },
- inertia: true,
- modifiers: [
- interact.modifiers.aspectRatio({
- // make sure the width is always double the height
- ratio: 1.72,
- modifiers: [
- interact.modifiers.restrictRect({ endOnly: true }),
- ],
- }),
- interact.modifiers.restrictSize({
- min: { width: 560, height: 337 }
- })
- ]
- })
- .draggable({
- listeners: {
- move (event) {
- let target = event.target;
- let x = (parseFloat(target.getAttribute("data-x")) || 0) + event.dx;
- let y = (parseFloat(target.getAttribute("data-y")) || 0) + event.dy;
- target.style.transform = \`translate(\${x}px, \${y}px)\`;
- target.setAttribute("data-x", x);
- target.setAttribute("data-y", y);
- },
- },
- inertia: true,
- modifiers: [
- interact.modifiers.restrictRect({
- restriction: "body",
- endOnly: true
- })
- ]
- })`;
- firstScriptTag.parentNode.insertBefore(interactModule, firstScriptTag)
- // updates the position of the video to match the log
- const updatePosition = () => {
- const timeSinceFirstPull = parseInt(streams[person].offset) + ((unsafeWindow.replayPosition - unsafeWindow.fights[0].start_time) / 1000);
- videoPlayer.seek(timeSinceFirstPull)
- }
- // returns if the player is allows to play
- const togglePlay = () => {
- allowPlay = !allowPlay;
- return !allowPlay;
- }
- // make the log play button play the video
- $("#play-button").attr("onclick", "toggleReplayState(this)").click((e) => {
- if (videoPlayer.isPaused()) {
- videoPlayer.play();
- togglePlay();
- } else {
- videoPlayer.pause();
- togglePlay();
- }
- });
- // updates video when moving via the replay bar
- $("#graph").on("mousedown", (e) => {
- if (e.type == "mousedown") {
- isDragging = true;
- updatePosition();
- }
- });
- $("body").on("mousemove mouseup", (e) => {
- if (videPlayerOpen && multiStreamOpen) {
- if (e.type == "mouseup") {
- if (isDragging) {
- isDragging = false;
- updatePosition();
- }
- }
- }
- })
- function stopLoading() {
- const loading = document.getElementById("multistream-loading-icon")
- if (loading) {
- loading.outerHTML = "";
- }
- const multistreamPlayer = document.getElementById("multistream_player");
- multistreamPlayer.style.display = "block";
- }
- function onTwitchPlayerReady() {
- updatePosition();
- videoPlayer.play()
- stopLoading();
- }
- function onTwitchPlayerEnded() {
- }
- function onTwitchPlayerPlaying() {
- if (!allowPlay) videoPlayer.pause()
- }
- function onTwitchPlayerPause() {
- if (allowPlay) videoPlayer.play()
- }
- function onTwitchPlayerSeek() {
- }
- function onYoutubePlayerReady(e) {
- stopLoading();
- }
- function onYoutubePlayerStateChange(e) {
- switch (e) {
- case 0: //onYouTubePlayerEnded
- break;
- case 1: //onYouTubePlayerPlaying
- break;
- case 2: //onYouTubePlayerPaused
- break;
- case 3: //onYouTubePlayerBuffering
- break;
- case 5: //onYouTubePlayerVideoCued
- break;
- default:
- break;
- }
- }
- function showMultiStreamPlayer() {
- if ($("#video-frame").css("display") === "block") {
- videoFrame = document.getElementById("video-frame-inner");
- videoFrame.innerHTML = "<div style='text-align:center; margin-top:100px' id='multistream-loading-icon'><p>Loading Video...</p><p><img src='https://assets.rpglogs.com/img/spinny.gif'></p></div>";
- let playerDiv = document.createElement("div");
- playerDiv.id = "multistream_player";
- videoFrame.innerHTML += playerDiv.outerHTML;
- playerDiv = document.getElementById("multistream_player");
- playerDiv.style = "width: 100%; height: 100%; display: none;";
- // If noddy is selected play fist stream;
- const stream = streams[person];
- const videoID = new URL(stream.url)
- if (stream.platform === 1) {
- videoPlayer = new Twitch.Player("multistream_player", {
- width: "100%",
- height: "100%",
- autoplay: false,
- muted: true,
- video: videoID.pathname.split("/")[2],
- });
- videoPlayer.addEventListener(Twitch.Player.READY, onTwitchPlayerReady);
- videoPlayer.addEventListener(Twitch.Player.ENDED, onTwitchPlayerEnded);
- videoPlayer.addEventListener(Twitch.Player.PLAYING, onTwitchPlayerPlaying);
- videoPlayer.addEventListener(Twitch.Player.PAUSE, onTwitchPlayerPause);
- videoPlayer.addEventListener(Twitch.Player.SEEK, onTwitchPlayerSeek);
- }
- if (stream.platform === 2) {
- videoPlayer = new YT.Player("multistream_player", {
- height: "100%",
- width: "100%",
- videoId: videoID.searchParams.get("v"),
- playerVars: {
- controls: 0
- }
- })
- videoPlayer.addEventListener("onReady", onYoutubePlayerReady);
- videoPlayer.addEventListener("onStateChange", onYoutubePlayerStateChange);
- }
- }
- }
- function toggleMultiStreamPlayer() {
- multiStreamOpen = !multiStreamOpen
- updateMultistreamButton()
- if (multiStreamOpen) {
- showMultiStreamPlayer()
- }
- }
- function updateMultistreamButton() {
- const multiStreamViewButton = document.getElementById("multistream-view")
- if (multiStreamOpen) {
- multiStreamViewButton.style.backgroundColor = "#000060"
- } else {
- multiStreamViewButton.style.backgroundColor = ""
- }
- }
- function showMultistreamOptions() {
- if (multiStreamOpen) {
- toggleMultiStreamPlayer()
- }
- videoFrame = document.getElementById("video-frame-inner");
- addGlobalEventListener("input", ".url-table-row", (e) => {
- const [user, action] = e.target.id.split("-");
- const value = e.target.value;
- if (action === "stream_url") {
- let url, platform;
- let err = false;
- try {
- url = new URL(value);
- if (url.hostname === "www.twitch.tv" && url.pathname.match(/\/videos\/\d+/g)) {
- platform = 1;
- } else if (url.hostname === "www.youtube.com" && url.pathname === "/watch" && url.searchParams.get("v")) {
- platform = 2;
- } else {
- throw "not a valid platform"
- }
- }
- catch (error) {
- if (streams[user]) {
- delete streams[user];
- }
- if (error) err = true;
- }
- finally {
- if (!err) {
- if (!streams[user]) streams[user] = {};
- streams[user].platform = platform;
- streams[user].url = url.href;
- }
- }
- }
- if (action === "stream_offset") {
- if (streams[user] && streams[user].url) {
- let offset = parseInt(value);
- streams[user].offset = offset;
- }
- }
- })
- const div = document.createElement("div");
- div.style = "text-align: center;";
- const form = document.createElement("form");
- form.style = "margin: 0;";
- form.acceptCharset = "utf-8";
- form.method = "GET";
- form.action = "javascript:void(0);";
- const table = document.createElement("table");
- table.style = "border-collapse: separate; border-spacing: 8px; margin: auto; text-align: left;";
- //Table Information
- const infoRow = document.createElement("tr");
- const nameInfo = document.createElement("td");
- const URLInfo = document.createElement("td");
- const offsetInfo = document.createElement("td");
- nameInfo.innerHTML = "Name:";
- URLInfo.innerHTML = "Stream URL:";
- offsetInfo.innerHTML = "Offset:";
- offsetInfo.style = "max-width: 60px;";
- infoRow.append(nameInfo, URLInfo, offsetInfo);
- table.appendChild(infoRow);
- const raidFrames = document.querySelectorAll(".raid-frame-contents");
- const raiders = [];
- raidFrames.forEach((frame) => {
- raiders.push(frame.innerHTML);
- })
- raiders.forEach((person) => {
- person = person.replace(" ", "_");
- const tableRow = document.createElement("tr");
- const nameData = document.createElement("td");
- nameData.innerHTML = person;
- tableRow.appendChild(nameData);
- const streamData = document.createElement("td");
- const streamUrl = document.createElement("input");
- streamUrl.style = "min-width: 260px;"
- streamUrl.type = "url";
- streamUrl.id = `${person}-stream_url`;
- streamUrl.name = `${person}-stream_url`;
- streamUrl.placeholder = "youtube.com/watch?v= or twitch.tv/videos/";
- if (streams[person] && streams[person].url) {
- streamUrl.setAttribute("value", streams[person].url);
- }
- streamUrl.classList.add("url-table-row");
- streamData.appendChild(streamUrl);
- tableRow.appendChild(streamData);
- const offsetData = document.createElement("td");
- const offsetTime = document.createElement("input");
- offsetTime.type = "number";
- offsetTime.style = "width: 5em;";
- offsetTime.id = `${person}-stream_offset`;
- offsetTime.name = `${person}-stream_offset`;
- offsetTime.placeholder = "# in sec";
- if (streams[person] && streams[person].offset) {
- offsetTime.setAttribute("value", streams[person].offset)
- }
- offsetTime.classList.add("url-table-row");
- offsetData.appendChild(offsetTime);
- tableRow.appendChild(offsetData);
- table.appendChild(tableRow);
- })
- form.appendChild(table);
- div.appendChild(form);
- videoFrame.innerHTML = div.outerHTML;
- }
- function toggleMultiStreamMove() {
- multiStreamMovement = !multiStreamMovement;
- const frameInner = document.getElementById("video-frame-inner");
- const resizeButton = document.getElementById("multistream-resize");
- if (multiStreamMovement) {
- resizeButton.style.backgroundColor = "#000060"
- frameInner.style.pointerEvents = "none"
- } else {
- resizeButton.style.backgroundColor = ""
- frameInner.style.pointerEvents = ""
- }
- }
- let prvPerson;
- document.addEventListener("click", (e) => {
- let selected = document.querySelector(".raid-frame.selected .raid-frame-contents");
- (selected) ? person = selected.innerHTML.replace(" ", "_") : person = Object.keys(streams)[0];
- if (prvPerson !== person) {
- if (multiStreamOpen) {
- showMultiStreamPlayer();
- }
- }
- prvPerson = person;
- })
- const positionGraph = document.getElementById("position-graph");
- const observer = new MutationObserver(function () {
- timesUpdate++;
- if (timesUpdate > 2) {
- if (!positionGraph.style.opacity) {
- const videoButton = document.querySelector(".replay-video");
- if (videPlayerOpen) {
- allowPlay = false;
- videoButton.click()
- videoButton.click()
- updateMultistreamButton()
- toggleMultiStreamMove()
- toggleMultiStreamMove()
- }
- }
- }
- });
- observer.observe(positionGraph, { attributes: true });
- videoButton.addEventListener("click", (e) => {
- $("#fullscreen-video, #remove-video, #select-video, #mute-video").remove();
- const frame = document.getElementById("video-frame");
- frame.style.touchAction = "none";
- frame.style.boxSizing = "border-box"
- videoFrame = document.getElementById("video-frame-inner");
- videPlayerOpen = !videPlayerOpen
- if (multiStreamOpen) {
- setTimeout(showMultiStreamPlayer, 500)
- }
- // Creates Menu Below Video Player
- const multiStreamOptions = document.getElementById("multistream-options");
- if (!multiStreamOptions) {
- const videoFrameControls = document.getElementById("video-frame-controls");
- videoFrameControls.style.cursor = "ns-resize"
- const multiStreamO = document.createElement("span");
- multiStreamO.style = "float: right; cursor: pointer;";
- multiStreamO.id = "multistream-options";
- multiStreamO.classList.add("graph-legend-button");
- multiStreamO.onclick = showMultistreamOptions;
- multiStreamO.innerText = "Multistream options"
- videoFrameControls.appendChild(multiStreamO);
- const multiStreamV = document.createElement("span");
- multiStreamV.style = "margin-right: -1px; float: right; cursor: pointer;";
- multiStreamV.id = "multistream-view";
- multiStreamV.classList.add("graph-legend-button");
- multiStreamV.onclick = toggleMultiStreamPlayer;
- multiStreamV.innerText = "Multistream View";
- videoFrameControls.appendChild(multiStreamV);
- const resizeButton = document.createElement("span");
- resizeButton.style = "float: left; cursor: pointer;";
- resizeButton.id = "multistream-resize";
- resizeButton.classList.add("graph-legend-button");
- resizeButton.onclick = toggleMultiStreamMove;
- resizeButton.innerText = "Resize/Move";
- videoFrameControls.appendChild(resizeButton);
- }
- })
- }
- // Checks if it's Chad Bradly's profile.
- if (/\/character\/id\/12781922/.test(location.pathname)) {
- // GIGACHAD
- $("#character-portrait-image").attr("src", "https://c.tenor.com/epNMHGvRyHcAAAAd/gigachad-chad.gif");
- // Adds custom banner background to my page.
- $("#portrait-and-basics, #character-header-customize-action-box, #update-box").addClass("slightly-transparent-box");
- $("#character-portrait-box").css("background-image", "url(\"https://i.imgur.com/dbwqHIt.png\")").addClass("with-banner");
- }
- })();