[Source Code] Random Girl Video Source Code Share

复制保存到html,浏览器打开即可使用。

源码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="theme-color" content="#000000">
<title>随机视频</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
  html, body { height: 100%; width: 100%; background: #000; overflow: hidden;
    font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif;
    color: #fff; user-select: none; }

  /* ====== 视频列表容器(纵向滚动,吸附切换) ====== */
  #feed {
    position: fixed; inset: 0;
    height: 100dvh; width: 100vw;
    overflow-y: scroll;
    scroll-snap-type: y mandatory;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  #feed::-webkit-scrollbar { display: none; }

  .slide {
    position: relative;
    height: 100dvh; width: 100vw;
    scroll-snap-align: start;
    scroll-snap-stop: always;
    background: #000;
    overflow: hidden;
  }

  video.video-el {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: contain;
    background: #000;
  }

  /* 渐变遮罩(底部信息可读) */
  .slide::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 38%;
    background: linear-gradient(to top, rgba(0,0,0,.55), rgba(0,0,0,0));
    pointer-events: none; z-index: 2;
  }

  /* ====== 加载 / 错误态 ====== */
  .status {
    position: absolute; inset: 0; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 14px; z-index: 3;
    color: rgba(255,255,255,.85); font-size: 14px;
  }
  .spinner {
    width: 44px; height: 44px; border-radius: 50%;
    border: 3px solid rgba(255,255,255,.2); border-top-color: #fe2c55;
    animation: spin .8s linear infinite;
  }
  @keyframes spin { to { transform: rotate(360deg); } }
  .retry-btn {
    margin-top: 6px; padding: 9px 22px; border-radius: 999px; border: none;
    background: #fe2c55; color: #fff; font-size: 14px; font-weight: 600;
    cursor: pointer;
  }

  /* ====== 右侧操作栏 ====== */
  .actions {
    position: absolute; right: 10px; bottom: 96px; z-index: 4;
    display: flex; flex-direction: column; align-items: center; gap: 20px;
  }
  .act { display: flex; flex-direction: column; align-items: center; gap: 5px; cursor: pointer; }
  .act .icon {
    width: 48px; height: 48px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: rgba(0,0,0,.25); backdrop-filter: blur(4px);
    transition: transform .15s, background .2s;
  }
  .act:active .icon { transform: scale(.88); }
  .act svg { width: 28px; height: 28px; fill: #fff; }
  .act.liked svg { fill: #fe2c55; }
  .act .label { font-size: 12px; color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,.6); }

  /* ====== 底部信息栏 ====== */
  .info {
    position: absolute; left: 14px; right: 78px; bottom: 76px; z-index: 4;
  }
  .info .uname { font-size: 17px; font-weight: 700; margin-bottom: 7px;
    text-shadow: 0 1px 4px rgba(0,0,0,.6); }
  .info .desc { font-size: 14px; line-height: 1.5; opacity: .95;
    text-shadow: 0 1px 4px rgba(0,0,0,.6); word-break: break-word; }
  .info .tag { color: #ffd1dc; }

  /* 底部播放进度条(顶部细线,抖音同款) */
  .progress {
    position: absolute; left: 0; right: 0; bottom: 0; height: 3px; z-index: 6;
    background: rgba(255,255,255,.18); pointer-events: none;
  }
  .progress-bar {
    height: 100%; width: 0%; background: #fe2c55; transition: width .12s linear;
  }

  /* 底部导航(装饰) */
  .navbar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 10;
    height: calc(54px + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    display: flex; background: #000; border-top: 1px solid rgba(255,255,255,.08);
  }
  .nav-item { flex: 1; display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: 3px; font-size: 11px; color: rgba(255,255,255,.6); }
  .nav-item.active { color: #fff; }
  .nav-item svg { width: 22px; height: 22px; fill: currentColor; }

  /* 桌面端居中手机框 */
  @media (min-width: 600px) {
    #feed { left: 50%; transform: translateX(-50%);
      width: 420px; height: min(100dvh, 760px); top: 50%; transform: translate(-50%,-50%);
      border-radius: 0; box-shadow: 0 0 80px rgba(0,0,0,.6); }
    .slide { height: min(100dvh, 760px); width: 420px; }
    .navbar { left: 50%; transform: translateX(-50%); width: 420px; }
  }

  /* 首次进入蒙层(浏览器自动播放策略) */
  #gate {
    position: fixed; inset: 0; z-index: 100; background: #000;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 22px; cursor: pointer;
  }
  #gate .play-tri {
    width: 86px; height: 86px; border-radius: 50%;
    background: rgba(254,44,85,.9); display: flex; align-items: center; justify-content: center;
    box-shadow: 0 0 40px rgba(254,44,85,.5);
  }
  #gate .play-tri::after {
    content: ""; border-left: 28px solid #fff; border-top: 18px solid transparent;
    border-bottom: 18px solid transparent; margin-left: 8px;
  }
  #gate p { color: rgba(255,255,255,.8); font-size: 14px; }
  .hidden { display: none !important; }
</style>
</head>
<body>

<!-- 首次点击蒙层 -->
<div id="gate">
  <div class="play-tri"></div>
  <p>点击进入 · 上下滑动切换</p>
</div>

<div id="feed"></div>

<!-- 底部导航(装饰,抖音风格) -->
<nav class="navbar">
  <div class="nav-item active">
    <svg viewBox="0 0 24 24"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
    首页
  </div>
  <div class="nav-item">
    <svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"/></svg>
    朋友
  </div>
  <div class="nav-item">
    <svg viewBox="0 0 24 24"><path d="M12 3v2.21l6 3.34V17h3v2H3v-2h3V8.55l6-3.34V3h0zm2 14v-6h-4v6h4z"/></svg>
    +
  </div>
  <div class="nav-item">
    <svg viewBox="0 0 24 24"><path d="M12 2C9.24 2 7 4.24 7 7c0 2.85 2.92 7.21 5 9.88 2.11-2.69 5-7 5-9.88 0-2.76-2.24-5-5-5zm0 7.5A2.5 2.5 0 1 1 12 4.5a2.5 2.5 0 0 1 0 5z"/></svg>
    消息
  </div>
  <div class="nav-item">
    <svg viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
    我
  </div>
</nav>

<script>
// =========================================================
// 抖音风格随机视频播放器 —— 纯前端独立版(无需后端)
//
// 说明:浏览器存在 CORS 跨域 + http 混合内容限制,无法直接调用
// 任意第三方接口。此处仅采用「原生支持 CORS(HTTPS)」的随机视频
// 接口,可直接在浏览器中跨域拉取。视频直链 CDN 同样允许跨域,
// 因此 <video> 可直接播放。
// =========================================================

// ---------- 可用 CORS 源(已实测:均返回 application/json + Access-Control-Allow-Origin:*)----------
// 每次请求都返回不同随机视频,两个源轮换 + 随机化保证内容不重复。
const SOURCES = [
  {
    id: 'xzdx',
    name: '小众独行',
    url: 'https://api.xzdx.top/xjj',
    // 返回 { data: { url: "https://...mp4" } }
    pick: d => d && d.data && d.data.url,
  },
  {
    id: 'dwo',
    name: '随机小姐姐',
    url: 'https://openapi.dwo.cc/api/ksvideo?type=json',
    // 返回 { pic: "http://...mp4", ... };http 在 https 页面会被拦截,需转 https
    pick: d => d && d.pic ? d.pic.replace(/^http:\/\//, 'https://') : null,
  },
];

const feed = document.getElementById('feed');
const gate = document.getElementById('gate');
const topSpacer = document.createElement('div');
feed.appendChild(topSpacer);

const INITIAL_SLIDES = 5;     // 首屏初始化条数
const KEEP_BEFORE = 2;        // 当前视频前方保留条数
const KEEP_AFTER = 5;         // 当前视频后方保留条数
let slots = [];               // 当前窗口内的 slide 槽位
let cursor = -1;              // 当前激活的逻辑下标
let firstIndex = 0;           // 当前窗口第一条的逻辑下标
let srcCursor = 0;            // 源轮换指针

// ---------- 拉取下一个视频直链 ----------
async function fetchNext() {
  // 轮换尝试所有源,任意一个成功即返回
  for (let t = 0; t < SOURCES.length; t++) {
    const src = SOURCES[srcCursor % SOURCES.length];
    srcCursor++;
    try {
      const r = await fetch(src.url, { cache: 'no-store' });
      if (!r.ok) continue;
      const data = await r.json();
      const url = src.pick(data);
      if (url) {
        return { ok: true, id: src.id, name: src.name, url };
      }
    } catch (e) {
      // 网络错误或 JSON 解析失败,尝试下一个源
    }
  }
  return { ok: false };
}

// ---------- 构建 slide ----------
function makeSlide() {
  const el = document.createElement('div');
  el.className = 'slide';
  el.innerHTML = `
    <video class="video-el" playsinline webkit-playsinline preload="auto"
           x5-playsinline poster=""></video>
    <div class="status"><div class="spinner"></div><div>加载中…</div></div>
    <div class="progress"><div class="progress-bar"></div></div>
    <div class="actions hidden">
      <div class="act like">
        <div class="icon"><svg viewBox="0 0 24 24"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg></div>
        <span class="label">赞</span>
      </div>
      <div class="act">
        <div class="icon"><svg viewBox="0 0 24 24"><path d="M21.99 4c0-1.1-.89-2-1.99-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4-.01-18zM18 14H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"/></svg></div>
        <span class="label">评论</span>
      </div>
      <div class="act">
        <div class="icon"><svg viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z"/></svg></div>
        <span class="label">分享</span>
      </div>
    </div>
    <div class="info hidden">
      <div class="uname"></div>
      <div class="desc"></div>
    </div>
  `;
  feed.appendChild(el);

  const slot = {
    el,
    video: el.querySelector('video'),
    status: el.querySelector('.status'),
    progressBar: el.querySelector('.progress-bar'),
    actions: el.querySelector('.actions'),
    info: el.querySelector('.info'),
    data: null,
    loading: null,
    disposed: false,
    index: 0,
    liked: false,
    active: false,
  };

  const v = slot.video;

  // ---- 一次性绑定视频事件(loading 显隐 + 进度 + 自动下一条)----
  v.addEventListener('loadstart', () => { if (slot.active) showStatus(slot, '加载中…'); });
  v.addEventListener('waiting',   () => { if (slot.active) showStatus(slot, '缓冲中…'); });
  ['canplay', 'playing', 'canplaythrough', 'seeked'].forEach(ev =>
    v.addEventListener(ev, () => { if (slot.active) hideStatus(slot); })
  );
  v.addEventListener('error', () => { if (slot.active) showError(slot, '视频加载失败,自动换一个…'); });
  v.addEventListener('timeupdate', () => {
    if (v.duration) slot.progressBar.style.width = (v.currentTime / v.duration * 100) + '%';
  });
  v.addEventListener('ended', () => {
    if (slot.index === cursor) {
      scrollToIndex(cursor + 1);
    }
  });

  // 点赞切换
  el.querySelector('.like').addEventListener('click', (e) => {
    e.stopPropagation();
    slot.liked = !slot.liked;
    el.querySelector('.like').classList.toggle('liked', slot.liked);
    el.querySelector('.like .label').textContent = slot.liked ? '已赞' : '赞';
  });

  return slot;
}

// ---------- 状态显示 ----------
function showStatus(slot, msg) {
  slot.status.classList.remove('hidden');
  slot.status.innerHTML = `<div class="spinner"></div><div>${msg || '加载中…'}</div>`;
}
function hideStatus(slot) { slot.status.classList.add('hidden'); }
function showError(slot, msg) {
  slot.status.classList.remove('hidden');
  slot.status.innerHTML = `<div>${msg || '加载失败'}</div>`;
}

// ---------- 加载视频数据到 slot ----------
async function loadInto(slot) {
  if (!slot || slot.disposed) return;
  if (slot.data) return;
  if (slot.loading) return slot.loading;
  slot.loading = (async () => {
    try {
      const data = await fetchNext();
      if (slot.disposed) return;
      if (!data.ok) {
        if (slot.active) showError(slot, '所有源暂不可用,稍后重试');
        return;
      }
      slot.data = data;
      slot.video.src = data.url;
      slot.video.load();
      slot.info.querySelector('.uname').textContent = '@' + data.name;
      slot.info.querySelector('.desc').innerHTML =
        `刷到的就是缘分 <span class="tag">#${data.name} #随机视频</span>`;
    } finally {
      slot.loading = null;
    }
  })();
  return slot.loading;
}

function getSlot(idx) {
  return slots.find(s => s.index === idx);
}

function releaseSlot(slot) {
  slot.active = false;
  slot.disposed = true;
  slot.video.pause();
  slot.video.removeAttribute('src');
  slot.video.load();
  slot.el.remove();
}

function setFeedOffset() {
  topSpacer.style.height = (firstIndex * feed.clientHeight) + 'px';
}

// ---------- 切换激活态 ----------
function activate(idx) {
  if (idx < 0) return;
  ensurePool(idx);
  if (idx === cursor) return;
  slots.forEach((s) => {
    s.active = false;
    if (s.index !== idx && s.video) s.video.pause();
  });

  cursor = idx;
  const slot = getSlot(idx);
  if (!slot) return;
  slot.active = true;
  slot.info.classList.remove('hidden');
  slot.actions.classList.remove('hidden');

  const tryPlay = () => slot.video.play().catch(() => {
    if (slot.active) showError(slot, '点击重试播放');
  });

  if (slot.video.readyState >= 3) {
    hideStatus(slot);
    tryPlay();
  } else {
    showStatus(slot, '加载中…');
    if (slot.video.src) tryPlay();
  }
  ensurePool();
}

// ---------- 维护有上限的无限滑动窗口 ----------
function ensurePool(center = cursor) {
  if (center < 0) center = 0;
  const from = Math.max(0, center - KEEP_BEFORE);
  const to = Math.max(INITIAL_SLIDES - 1, center + KEEP_AFTER);

  for (let i = from; i <= to; i++) {
    if (getSlot(i)) continue;
    const s = makeSlide();
    s.index = i;
    slots.push(s);
    loadInto(s);
  }

  slots
    .filter(s => s.index < from || s.index > to)
    .forEach(releaseSlot);

  slots = slots
    .filter(s => s.index >= from && s.index <= to)
    .sort((a, b) => a.index - b.index);

  slots.forEach(s => feed.appendChild(s.el));
  firstIndex = slots[0] ? slots[0].index : 0;
  setFeedOffset();
}

function scrollToIndex(idx) {
  if (idx < 0) return;
  ensurePool(idx);
  feed.scrollTo({ top: idx * feed.clientHeight, behavior: 'smooth' });
}

// ---------- 滚动检测(吸附切换) ----------
let snapTimer;
feed.addEventListener('scroll', () => {
  clearTimeout(snapTimer);
  snapTimer = setTimeout(() => {
    const idx = Math.round(feed.scrollTop / feed.clientHeight);
    activate(idx);
  }, 90);
}, { passive: true });

window.addEventListener('resize', () => {
  setFeedOffset();
  if (cursor >= 0) feed.scrollTo({ top: cursor * feed.clientHeight });
}, { passive: true });

// ---------- 键盘(桌面端) ----------
document.addEventListener('keydown', (e) => {
  if (e.key === 'ArrowDown' || e.key === 'j') {
    scrollToIndex(cursor + 1);
  } else if (e.key === 'ArrowUp' || e.key === 'k') {
    if (cursor > 0) scrollToIndex(cursor - 1);
  } else if (e.key === ' ' && cursor >= 0) {
    e.preventDefault();
    const slot = getSlot(cursor);
    if (!slot) return;
    const v = slot.video;
    if (v.paused) v.play(); else v.pause();
  }
});

// ---------- 启动 ----------
async function boot() {
  ensurePool(0);
  await loadInto(getSlot(0));
  activate(0);
}

gate.addEventListener('click', () => {
  gate.classList.add('hidden');
  boot();
});
</script>
</body>
</html>
3 Likes

Helpful info here. Makes perfect sense. This is worth noting. Reference ID: 94296-qnk01j.

6哇 :xhj27:

穿太多了,差评

1 Like

66666

确实穿的太多了

能不能接入p**nhub源 :xhj001:

私发我源 :xhj11:

Thanks for sharing!

Sometimes, just something nice to look at

1 Like

Bad Request

Your browser sent a request that this server could not understand.

Reference #7.1b7b3517.1781703671.8dcc3612

可用,感谢分享

可以 可以 感谢佬的分享

:+1::folded_hands:​好东西

66 impressive

6666666

学习一下