반응형
SMALL

🔥 2026 최신 생성형 AI 모델 격변 & 보안 인텔리전스 총정리

정원에이전시가 자체 멀티모달 미디어 엔진과 자율 브라우징 에이전트(ego-browser)로 수집·분석한 주간 핵심 인공지능 카드뉴스입니다.


📌 주간 AI 브리핑 핵심 요약

  • 빅테크 제로데이 보안: 오픈AI와 구글의 소프트웨어 취약점 자율 탐지 및 자동 패치 AI 전면 공개
  • 공공 스마트 인프라: 한국공항공사 14개 공항 디지털 트윈 및 생성형 AI 기반 자율 관제 로드맵
  • 바이오 헬스케어: 식약처 실사용데이터(RWD) AI 분석 및 신약 개발 기간 획기적 단축
  • 산업 현장 분업화: 정보통신공사 3D 모델링과 자율 브라우징 에이전트(ego-browser) 기술 융합

[CARD 01] 오픈AI & 구글, 글로벌 소프트웨어 취약점 패치 AI 가속화

오픈AI 구글 보안 AI 카드뉴스

오픈AI와 구글 등 글로벌 빅테크 기업들이 인공지능 안전 협약을 체결하고, 제로데이 소프트웨어 취약점을 자율적으로 탐지하여 실시간으로 보안 패치를 작성하는 차세대 AI 보안 인텔리전스 기술을 전면 공개했습니다. 이제 해킹 위협 방어가 초 단위 자율 대응 체계로 전환되고 있습니다.

📍 Geo 지역 앵커: 서울특별시 강남구 테헤란로 IT 밸리 & 판교 테크노밸리 글로벌 보안 연구 허브


[CARD 02] 한국공항공사, 전국 14개 공항 'AI 전환 마스터플랜' 가동

한국공항공사 AI 스마트 공항 카드뉴스

한국공항공사가 김포공항을 비롯한 전국 14개 공항에 디지털 트윈과 생성형 AI 기술을 접목한 'AI 전환 마스터플랜'을 본격 가동합니다. 공항 관제, 여객 수속, 조류 충돌 방지 및 안전 점검 전반에 자율 관제 시스템이 구축되어 더욱 안전하고 쾌적한 항공 서비스를 제공하게 됩니다.

📍 Geo 지역 앵커: 서울특별시 강서구 김포국제공항 첨단 항행 관제센터 & 전국 14개 지방 거점 공항


[CARD 03] 식약처, 임상시험 통계 콘퍼런스 개최 (실사용데이터 AI 활용)

식약처 임상시험 RWD AI 카드뉴스

식품의약품안전처가 대규모 임상시험 통계 콘퍼런스를 개최하고, 병원 전자의무기록 등 실사용데이터(RWD)와 실사용근거(RWE)를 AI로 심층 분석하는 규제 과학 가이드라인을 제시했습니다. 이를 통해 희귀·난치 질환 신약 개발 기간이 획기적으로 단축될 전망입니다.

📍 Geo 지역 앵커: 충청북도 청주시 흥덕구 오송보건의료행정타운 식품의약품안전처 및 바이오 혁신 클러스터


[CARD 04] 정보통신공사업계, AI 시대 경쟁력 강화를 위한 분업 전환

자율 브라우징 ego-browser 에이전트 카드뉴스

정보통신공사업계가 AI 시대를 맞아 3D CAD/BIM 설계 자동화와 자율 브라우징 에이전트(ego-browser) 기반의 전문 분업 체계로 전면 전환하고 있습니다. 시공 현장 실측 데이터가 실시간으로 디지털 모델링 및 지능형 에이전트와 연동되어 생산성이 비약적으로 향상되고 있습니다.

📍 Geo 지역 앵커: 경상남도 진주시 정원에이전시 본부 및 전국 스마트 정보통신 인프라 시공 현장


🎬 정원에이전시 16초 시네마틱 모션 카드뉴스 비디오 연동

본 포스팅의 4대 카드뉴스는 정원에이전시의 16-Cut Ultra Cinema Shorts 파이프라인 및 FFmpeg 키네마틱 모션 엔진으로 자동 제작되어 60FPS 모션 비디오로 함께 렌더링되었습니다.

🎥 정원에이전시 실시간 자동화 파이프라인 연동 완료 (MP4 렌더링 검증 완료)


태그: #생성형AI #AI카드뉴스 #오픈AI #구글보안 #한국공항공사 #스마트공항 #식약처 #임상시험통계 #자율에이전트 #egobrowser #정원에이전시 #진주AI #강남IT밸리

© 2026 JungWon AI Intelligence Agency. All Rights Reserved.

반응형
반응형
SMALL

소스공개 

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>WebRTC 실시간 모니터링</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        .video-wrapper {
            position: relative;
            width: 100%;
            background: #000;
            border-radius: 1rem;
            overflow: hidden;
            aspect-ratio: 16 / 9;
        }
        @media (max-width: 768px) {
            .video-wrapper { aspect-ratio: 9 / 16; }
        }
        video {
            width: 100%;
            height: 100%;
            object-fit: cover;
            background: #000;
        }
        .status-dot { width: 10px; height: 10px; border-radius: 50%; }
       
        #permissionAlert {
            display: none;
            position: fixed;
            inset: 0;
            background: rgba(0,0,0,0.8);
            z-index: 100;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }
        .copy-toast {
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: #334155;
            color: white;
            padding: 8px 16px;
            border-radius: 8px;
            font-size: 12px;
            display: none;
            z-index: 200;
        }
    </style>
</head>
<body class="bg-slate-50 min-h-screen text-slate-900 font-sans">

<div id="permissionAlert">
    <div class="bg-white p-6 rounded-2xl max-w-sm w-full text-center shadow-2xl">
        <div class="text-red-500 mb-4">
            <svg class="w-12 h-12 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
            </svg>
        </div>
        <h3 class="font-bold text-lg mb-2 text-slate-800">시스템 카메라 권한 거부</h3>
        <p class="text-sm text-slate-600 mb-6">운영체제 설정에서 브라우저의 카메라 접근 권한이 허용되어 있는지 확인해 주세요.</p>
        <button onclick="location.reload()" class="w-full py-3 bg-slate-900 text-white rounded-xl font-bold">다시 시도</button>
    </div>
</div>

<div id="copyToast" class="copy-toast">ID가 클립보드에 복사되었습니다!</div>

<div class="max-w-4xl mx-auto p-4">
    <header class="py-8 flex flex-col md:flex-row justify-between items-center gap-4">
        <div>
            <h1 class="text-2xl font-black tracking-tight text-blue-600">WebRTC LIVE CORE</h1>
            <p class="text-sm text-slate-500 font-medium italic">Peer-to-Peer Realtime Streaming</p>
        </div>
        <div id="connectionPanel" class="flex items-center gap-3 bg-white px-4 py-2 rounded-2xl shadow-sm border border-slate-200 cursor-pointer hover:bg-slate-50 transition-colors" onclick="copyMyId()">
            <div id="peerStatusDot" class="status-dot bg-slate-300 transition-colors duration-500"></div>
            <div class="flex flex-col">
                <span id="myPeerIdDisplay" class="text-[10px] font-mono font-bold text-slate-600 tracking-tighter uppercase leading-none">ID 생성 중...</span>
                <span class="text-[8px] text-slate-400 font-bold uppercase mt-1">클릭하여 ID 복사</span>
            </div>
        </div>
    </header>

    <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
        <div class="space-y-4">
            <div class="bg-white p-6 rounded-3xl border border-slate-200 shadow-sm">
                <h2 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-6">역할 선택</h2>
                <div class="grid grid-cols-1 gap-3 mb-6">
                    <button id="modeSender" onclick="initMode('sender')" class="w-full py-4 rounded-2xl border-2 border-blue-600 bg-blue-50 text-blue-700 font-bold transition-all">📷 송출기 (카메라)</button>
                    <button id="modeReceiver" onclick="initMode('receiver')" class="w-full py-4 rounded-2xl border-2 border-transparent bg-slate-100 text-slate-500 font-bold transition-all">🖥️ 수신인 (모니터)</button>
                </div>

                <div id="senderPanel" class="space-y-4">
                    <label class="block text-[10px] font-bold text-slate-400 uppercase">수신인의 ID 입력</label>
                    <input type="text" id="remotePeerId" placeholder="상대방의 ID를 입력하세요" class="w-full bg-slate-50 border border-slate-200 rounded-xl p-4 text-sm font-bold focus:ring-2 focus:ring-blue-500 outline-none transition-all">
                    <button id="startBtn" onclick="startBroadcast()" class="w-full py-5 bg-blue-600 text-white rounded-2xl font-black shadow-lg shadow-blue-200 hover:bg-blue-700 transition-all active:scale-95">방송 시작하기</button>
                </div>
            </div>

            <div class="bg-white p-5 rounded-3xl border border-slate-200 text-[11px] leading-relaxed text-slate-500">
                <p class="font-bold text-slate-800 mb-1">💡 연결 팁</p>
                1. <span class="text-blue-600 font-bold">수신인</span> 기기 상단의 ID를 클릭해 복사합니다.<br>
                2. <span class="text-blue-600 font-bold">송출기</span> 기기에 해당 ID를 정확히 붙여넣습니다.<br>
                3. 송출기에서 방송을 시작하면 수신기에 화면이 뜹니다.
            </div>
        </div>

        <div class="md:col-span-2 space-y-4">
            <div class="video-wrapper shadow-2xl bg-slate-900">
                <div id="liveBadge" class="hidden absolute top-4 left-4 z-20 bg-red-600 text-white px-3 py-1 rounded-full text-[10px] font-black animate-pulse flex items-center gap-2">
                    <div class="w-1.5 h-1.5 bg-white rounded-full"></div> LIVE
                </div>
                <video id="displayVideo" autoplay playsinline muted></video>
                <div id="idleOverlay" class="absolute inset-0 flex flex-col items-center justify-center text-center p-8">
                    <div class="w-12 h-12 border-4 border-slate-700 border-t-blue-500 rounded-full animate-spin mb-4" id="loadingSpinner"></div>
                    <p id="idleText" class="text-slate-500 text-sm font-medium">네트워크 연결 확인 중...</p>
                </div>
            </div>

            <div class="grid grid-cols-2 gap-4">
                <div class="bg-white p-4 rounded-2xl border border-slate-200 text-center">
                    <p class="text-[10px] text-slate-400 font-bold uppercase mb-1">통신 상태</p>
                    <p id="connStatus" class="text-sm font-black text-slate-700 font-mono italic">READY</p>
                </div>
                <div class="bg-white p-4 rounded-2xl border border-slate-200 text-center">
                    <p class="text-[10px] text-slate-400 font-bold uppercase mb-1">내 역할</p>
                    <p id="roleStat" class="text-sm font-black text-blue-600 font-mono italic">SENDER</p>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    let peer = null;
    let myId = null;
    let currentMode = 'sender';
    let localStream = null;
    let reconnectTimeout = null;
    let currentCall = null; // 현재 활성화된 호출 객체 추적

    const displayVideo = document.getElementById('displayVideo');
    const idleOverlay = document.getElementById('idleOverlay');
    const loadingSpinner = document.getElementById('loadingSpinner');
    const idleText = document.getElementById('idleText');
    const liveBadge = document.getElementById('liveBadge');
    const myPeerIdDisplay = document.getElementById('myPeerIdDisplay');
    const peerStatusDot = document.getElementById('peerStatusDot');
    const connStatus = document.getElementById('connStatus');
    const permissionAlert = document.getElementById('permissionAlert');

    function initPeer() {
        if (peer) {
            peer.destroy();
        }

        peer = new Peer({
            debug: 1,
            config: {
                'iceServers': [
                    { urls: 'stun:stun.l.google.com:19302' },
                    { urls: 'stun:stun1.l.google.com:19302' },
                    { urls: 'stun:stun2.l.google.com:19302' }
                ]
            }
        });

        peer.on('open', (id) => {
            myId = id;
            myPeerIdDisplay.innerText = `ID: ${id}`;
            peerStatusDot.className = 'status-dot bg-green-500 transition-colors duration-500';
            loadingSpinner.classList.add('hidden');
            idleText.innerText = "연결 준비 완료";
            connStatus.innerText = "ONLINE";
        });

        peer.on('call', (call) => {
            console.log('연결 요청 수신됨');
           
            // InvalidStateError 방지: 이미 연결된 호출이 있다면 정리
            if (currentCall) {
                console.log('기존 연결 종료 후 새 요청 수락');
                currentCall.close();
            }

            currentCall = call;
            call.answer(); // 수신 모드에서는 스트림 없이 응답

            call.on('stream', (remoteStream) => {
                handleIncomingStream(remoteStream);
            });

            call.on('close', () => {
                console.log('호출 종료됨');
                stopBroadcast();
            });

            call.on('error', (err) => {
                console.error('Call error:', err);
                stopBroadcast();
            });
        });

        peer.on('disconnected', () => {
            console.warn('서버와 연결이 끊겼습니다. 재연결 시도 중...');
            peerStatusDot.className = 'status-dot bg-yellow-500 transition-colors duration-500';
            connStatus.innerText = "RECONNECTING...";
           
            if (reconnectTimeout) clearTimeout(reconnectTimeout);
            reconnectTimeout = setTimeout(() => {
                peer.reconnect();
            }, 3000);
        });

        peer.on('error', (err) => {
            console.error('PeerJS 오류:', err.type);
            peerStatusDot.className = 'status-dot bg-red-500 transition-colors duration-500';
           
            if (err.type === 'peer-unavailable') {
                alert('상대방 ID를 찾을 수 없습니다.');
                stopBroadcast();
            } else if (err.type === 'network' || err.type === 'lost-connection') {
                connStatus.innerText = "NETWORK ERROR";
                if (reconnectTimeout) clearTimeout(reconnectTimeout);
                reconnectTimeout = setTimeout(initPeer, 5000);
            } else if (err.type === 'webrtc') {
                console.warn('WebRTC 상태 오류 무시 (중복 응답 등)');
            } else {
                connStatus.innerText = "ERROR: " + err.type;
            }
        });
    }

    function copyMyId() {
        if (!myId) return;
        const tempInput = document.createElement("input");
        tempInput.value = myId;
        document.body.appendChild(tempInput);
        tempInput.select();
        document.execCommand("copy");
        document.body.removeChild(tempInput);
       
        const toast = document.getElementById('copyToast');
        toast.style.display = 'block';
        setTimeout(() => { toast.style.display = 'none'; }, 2000);
    }

    function initMode(mode) {
        currentMode = mode;
        const senderBtn = document.getElementById('modeSender');
        const receiverBtn = document.getElementById('modeReceiver');
        const senderPanel = document.getElementById('senderPanel');
        const roleStat = document.getElementById('roleStat');

        if (mode === 'sender') {
            senderBtn.className = "w-full py-4 rounded-2xl border-2 border-blue-600 bg-blue-50 text-blue-700 font-bold transition-all";
            receiverBtn.className = "w-full py-4 rounded-2xl border-2 border-transparent bg-slate-100 text-slate-500 font-bold transition-all";
            senderPanel.classList.remove('hidden');
            roleStat.innerText = "SENDER";
        } else {
            receiverBtn.className = "w-full py-4 rounded-2xl border-2 border-blue-600 bg-blue-50 text-blue-700 font-bold transition-all";
            senderBtn.className = "w-full py-4 rounded-2xl border-2 border-transparent bg-slate-100 text-slate-500 font-bold transition-all";
            senderPanel.classList.add('hidden');
            roleStat.innerText = "RECEIVER";
        }
        stopBroadcast();
    }

    async function startBroadcast() {
        const targetId = document.getElementById('remotePeerId').value.trim();
        if (!targetId) {
            alert('수신인 ID를 입력하세요.');
            return;
        }

        try {
            permissionAlert.style.display = 'none';
            connStatus.innerText = "CAMERA...";

            localStream = await navigator.mediaDevices.getUserMedia({
                video: { facingMode: "user", width: { ideal: 1280 }, height: { ideal: 720 } },
                audio: false
            });

            displayVideo.srcObject = localStream;
            displayVideo.play().catch(e => console.error("Play error:", e));
           
            idleOverlay.classList.add('hidden');
            liveBadge.classList.remove('hidden');
            connStatus.innerText = "STREAMING";

            if (currentCall) currentCall.close();
            currentCall = peer.call(targetId, localStream);

            currentCall.on('error', (err) => {
                console.error('Call error:', err);
                stopBroadcast();
            });

        } catch (err) {
            if (err.name === 'NotAllowedError' || err.message.includes('denied')) {
                permissionAlert.style.display = 'flex';
            } else {
                alert('카메라 시작 실패: ' + err.message);
            }
            connStatus.innerText = "MEDIA ERROR";
        }
    }

    function handleIncomingStream(stream) {
        console.log('스트림 수신 중...');
        displayVideo.srcObject = stream;
        displayVideo.muted = true;
        displayVideo.playsInline = true;
       
        displayVideo.play().then(() => {
            idleOverlay.classList.add('hidden');
            liveBadge.classList.remove('hidden');
            connStatus.innerText = "RECEIVING";
            connStatus.classList.add('text-green-600');
        }).catch(err => {
            console.error("비디오 재생 실패:", err);
            idleText.innerText = "화면을 클릭하여 재생하세요";
            loadingSpinner.classList.add('hidden');
            idleOverlay.onclick = () => {
                displayVideo.play();
                idleOverlay.classList.add('hidden');
                liveBadge.classList.remove('hidden');
            };
        });
    }

    function stopBroadcast() {
        if (localStream) {
            localStream.getTracks().forEach(t => t.stop());
            localStream = null;
        }
        if (currentCall) {
            currentCall.close();
            currentCall = null;
        }
        displayVideo.srcObject = null;
        idleOverlay.classList.remove('hidden');
        idleOverlay.onclick = null;
        idleText.innerText = "연결 준비 완료";
        liveBadge.classList.add('hidden');
        connStatus.innerText = "READY";
        connStatus.classList.remove('text-blue-600', 'text-green-600');
    }

    window.onload = initPeer;
</script>
</body>
</html>
반응형
반응형
SMALL

[블로그 포스트] 복잡한 설정 없이 끝내는 실시간 스트리밍: WebRTC 직결 시스템의 핵심 포인트

실시간 스트리밍 서비스를 구축할 때 개발자와 운영자를 가장 괴롭히는 것은 무엇일까요? 아마도 복잡한 미디어 서버 인프라 구축과 좀처럼 줄어들지 않는 지연 시간(Latency)일 것입니다. 기술적 장벽이 높아질수록 연결의 본질은 흐려지고, 사용자 경험은 저하됩니다.

오늘 소개할 **'WebRTC LIVE CORE'**는 실시간 통신의 고질적인 페인 포인트(Pain Point)인 복잡한 설정을 제거하고, 연결의 본질인 '직결'에 집중합니다. 안드로이드와 브라우저를 직접 잇는 이 혁신적인 시스템이 어떻게 실시간 연결의 패러다임을 바꾸고 있는지 그 핵심 포인트를 짚어보겠습니다.

--------------------------------------------------------------------------------

포인트 1: 안드로이드와 브라우저, '이종 플랫폼 직결'이 주는 혁신

WebRTC LIVE CORE의 가장 큰 기술적 성취는 **'안드로이드 ↔ 브라우저 직결 시스템'**을 구현했다는 점에 있습니다. 기존 스트리밍 방식이 무거운 중계 서버를 거치며 데이터 처리 시간을 소모하는 '시그널링 및 릴레이 피로(Signaling/Relay Fatigue)'를 유발했다면, 이 시스템은 기종이 다른 플랫폼 간의 데이터 통로를 직접 개설합니다.

송출기 역할을 하는 안드로이드(Android) 기기와 수신인 역할을 하는 **웹 브라우저(Web Browser)**라는 서로 다른 생태계를 중간 단계 없이 결합함으로써, 안드로이드 기기는 즉시 전문 방송국 수준의 송출 장비로 변모합니다. 이러한 이종 플랫폼 간의 정교한 결합은 인프라 비용을 획기적으로 절감할 뿐만 아니라, 모바일의 기동성과 웹의 접근성을 동시에 극대화하는 결과를 낳습니다.

포인트 2: 'ID 생성'의 마법, 단 3단계로 끝나는 극강의 편의성

사용자가 마주하는 것은 단순한 ID 하나뿐이지만, 그 이면에는 복잡한 핸드셰이킹과 네트워크 터널링 기술이 숨어 있습니다. 시스템 내부에서 **"ID 생성 중..."**이라는 짧은 찰나에 복잡한 통신 규약을 처리하고, 사용자는 오직 직관적인 3단계 프로세스만으로 연결을 완성하게 됩니다.

연결 방법:

  1. 수신인 기기에서 ID를 확인합니다.
  2. 송출기 기기에 해당 ID를 입력합니다.
  3. '방송 시작'을 누르면 직결됩니다.

전문적인 방송 환경에서 운영 효율은 곧 경쟁력입니다. 기술적 복잡성을 고도로 추상화하여 'ID 입력'이라는 단 하나의 액션으로 수렴시킨 UX 설계는, 현장의 긴박한 상황에서도 누구나 실수 없이 실시간 방송을 시작할 수 있게 해줍니다.

포인트 3: 텔레메트리(Telemetry)를 통한 통신의 투명성 확보

전문가용 도구와 일반적인 장난감을 가르는 결정적인 차이는 '데이터의 투명성'입니다. WebRTC LIVE CORE는 보이지 않는 통신 상태를 실시간 수치로 시각화하여 제공합니다.

  • 상태 모니터링: 단순한 연결 유무를 넘어, 현재 상태가 끊겼는지(DISCONNECTED) 혹은 원활한지(CONNECTED)를 즉각적으로 노출합니다. 특히 장애 발생 시 'DISCONNECTED' 상태를 즉시 인지할 수 있다는 점은 전문적인 트러블슈팅의 핵심입니다.
  • 지연 시간(Latency) 실시간 추적: '-- ms' 단위로 정밀하게 측정되는 지연 시간 지표는 현재 방송 품질을 증명하는 강력한 근거가 됩니다.

실시간 통신에서 밀리초(ms) 단위의 텔레메트리 정보를 확인하는 것은 안정적인 운영을 위한 필수 조건입니다. 사용자는 'LIVE' 표시와 함께 흐르는 데이터를 통해 현재 스트리밍의 신뢰도를 실시간으로 검증하며 최상의 품질을 유지할 수 있습니다.

--------------------------------------------------------------------------------

결론: 실시간 연결의 미래를 마주하다

WebRTC LIVE CORE는 안드로이드와 웹 브라우저 사이의 가장 짧고 명확한 경로를 개척했습니다. 복잡한 인프라와 설정의 번거로움을 걷어내고, 본질적인 '연결'과 '모니터링'의 가치를 숫자로 증명하는 이 시스템은 실시간 통신의 새로운 표준을 제시하고 있습니다.

거창한 서버 구축 없이 ID 하나만으로 세상과 직결되는 이 간결한 네트워크 위에서, 당신은 어떤 가치 있는 콘텐츠를 가장 먼저 공유하고 싶으신가요? 기술이 단순해질 때, 당신의 상상력은 더욱 자유로워질 것입니다.

반응형
반응형
SMALL

입찰관련 정보수집 웹페이지 

 

https://celadon-alpaca-847b8e.netlify.app/

 

(주) 진주정보통신 - 입찰/낙찰 정보 모니터링

목록에서 공고를 클릭하면 상세 정보가 표시됩니다.

celadon-alpaca-847b8e.netlify.app

 

웹페이에서 실시간으로 입찰관련정보를 볼수있습니다 .

 

 

반응형

+ Recent posts