Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Webでできる体験を考える会 / On the possibilities of PWA experience

Webでできる体験を考える会 / On the possibilities of PWA experience

LINE Developers
PRO

February 01, 2020
Tweet

More Decks by LINE Developers

Other Decks in Technology

Transcript

  1. 8FCͰͰ͖ΔମݧΛߟ͑Δձ
    折原 レオナルド賢

    View Slide

  2. ڈ೥ͷ͍·͝Ζ

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. ࿩͢͜ͱ
    ͍ΖΜͳ 8FC"1*Λ৮ͬͯΈΔ
    ࢖ͬͨ͜ͱͳ͍ਓͰ΋Θ͔Γ΍͘͢
    Ͱ΋ʮߟ͑ΔձʯͳͷͰൃలܕ΋ఴ͑ͯ

    View Slide

  7. ࿩͢τϐοΫ
    ໐Δ΋ͷɾಈ͘΋ͷͷ"1*
    8FC"TTFNCMZͱ 8FC8PSLFS
    1VTI"1*

    View Slide

  8. 8FC"VEJP"1*8FC(-
    8FC#MVFUPPUI8FC.*%*"1*
    ໐Δ΋ͷɾಈ͘΋ͷͷ"1*

    View Slide

  9. ໐Δ΋ͷ

    View Slide

  10. 8FC"VEJP"1*͜ͱ͸͡Ί
    const context: AudioContext = new (window.AudioContext || window.webkitAudioContext)();
    AudioContext Λ࡞੒͢Δͱ͜Ζ͔Β͸͡·Δ

    View Slide

  11. 8FC"VEJP"1*͜ͱ͸͡Ί
    const context: AudioContext = new (window.AudioContext || window.webkitAudioContext)();
    const sourceNode: AudioBufferSourceNode = context.createBufferSource();
    const gainNode: GainNode = context.createGain();
    const biquadFilterNode: BiquadFilterNode = context.createBiquadFilter();
    const analyserNode: AnalyserNode = context.createAnalyser();
    biquad
    Filter
    Node
    source
    Node
    gain
    Node
    analyser
    Node

    View Slide

  12. 8FC"VEJP"1*͜ͱ͸͡Ί
    const context: AudioContext = new (window.AudioContext || window.webkitAudioContext)();
    const sourceNode: AudioBufferSourceNode = context.createBufferSource();
    const gainNode: GainNode = context.createGain();
    const biquadFilterNode: BiquadFilterNode = context.createBiquadFilter();
    const analyserNode: AnalyserNode = context.createAnalyser();
    biquad
    Filter
    Node
    source
    Node
    gain
    Node
    analyser
    Node
    "VEJP#VGGFS4PVSDF/PEF
    TUBSU XIFO PGGTFU EVSBUJPO

    TUPQ XIFO

    CVGGFSԻָσʔλ BT"VEJP#VGGFS
    MPPQUSVF
    ԻݯΛઃఆɾૢ࡞

    View Slide

  13. 8FC"VEJP"1*͜ͱ͸͡Ί
    const context: AudioContext = new (window.AudioContext || window.webkitAudioContext)();
    const sourceNode: AudioBufferSourceNode = context.createBufferSource();
    const gainNode: GainNode = context.createGain();
    const biquadFilterNode: BiquadFilterNode = context.createBiquadFilter();
    const analyserNode: AnalyserNode = context.createAnalyser();
    biquad
    Filter
    Node
    source
    Node
    gain
    Node
    analyser
    Node
    (BJO/PEF
    HBJOWBMVF ʙ
    ԻྔΛίϯτϩʔϧ

    View Slide

  14. 8FC"VEJP"1*͜ͱ͸͡Ί
    const context: AudioContext = new (window.AudioContext || window.webkitAudioContext)();
    const sourceNode: AudioBufferSourceNode = context.createBufferSource();
    const gainNode: GainNode = context.createGain();
    const biquadFilterNode: BiquadFilterNode = context.createBiquadFilter();
    const analyserNode: AnalyserNode = context.createAnalyser();
    biquad
    Filter
    Node
    source
    Node
    gain
    Node
    analyser
    Node
    #JRVBE'JMUFS/PEF
    UZQF
    ༻ҙ͞ΕͨΤϑΣΫλʔΛֻ͚Δ
    GSFRVFODZWBMVF
    ʙ
    2WBMVF ʿ ʙ ʿ
    ʞMPXQBTTʟ
    ʞIJHIQBTTʟ
    ʞCBOEQBTTʟ
    ʞMPXTIFMGʟ
    ʞIJHITIFMGʟ
    FUD

    View Slide

  15. 8FC"VEJP"1*͜ͱ͸͡Ί
    const context: AudioContext = new (window.AudioContext || window.webkitAudioContext)();
    const sourceNode: AudioBufferSourceNode = context.createBufferSource();
    const gainNode: GainNode = context.createGain();
    const biquadFilterNode: BiquadFilterNode = context.createBiquadFilter();
    const analyserNode: AnalyserNode = context.createAnalyser();
    biquad
    Filter
    Node
    source
    Node
    gain
    Node
    analyser
    Node
    "OBMZTFS/PEF
    ԻͷσʔλΛऔಘ͢Δ
    const analyzerParams = {
    times: new Uint8Array(analyserNode.frequencyBinCount),
    freqs: new Uint8Array(48),
    };
    analyserNode.getByteTimeDomainData(analyzerParams.times);
    analyserNode.getByteFrequencyData(analyzerParams.freqs);

    View Slide

  16. getByteTimeDomainData ͰऔಘͰ͖Δσʔλ
    getByteFrequencyData ͰऔಘͰ͖Δσʔλ

    View Slide

  17. ֤ /PEFͷ഑ઢ
    biquad
    Filter
    Node
    source
    Node
    gain
    Node
    analyser
    Node
    sourceNode.buffer = SOUND_BUFFER;
    sourceNode.connect(gainNode);
    gainNode.connect(biquadFilterNode);
    biquadFilterNode.connect(analyserNode);
    analyserNode.connect(context.destination);
    connect connect
    connect
    context.destination
    connect
    SOUND_BUFFER

    View Slide

  18. 4PVOE8BMLFSͷ഑ઢ

    View Slide

  19. 4PVOE8BMLFSͷ഑ઢ
    IUUQTZPVUVCF&:X089CCOL

    View Slide

  20. 4PVOE8BMLFSͷ഑ઢ
    IUUQTZPVUVCF9I%1D53VT

    View Slide

  21. 4PVOE8BMLFSͷ഑ઢ
    IUUQTZPVUVCF(1%OT$FG7I&

    View Slide

  22. ಈ͘΋ͷ

    View Slide

  23. 8FC(-͜ͱ͸͡Ί
    ͞ΘͬͯΈΔͳΒϥΠϒϥϦ͕γϯϓϧ

    View Slide

  24. 8FC(-͜ͱ͸͡Ί
    IUUQTEFNPTMJUUMFXPSLTIPQGSUSBDL IUUQTJNNFSTJWFHDPNXJTIFT

    View Slide

  25. 8FC(-͜ͱ͸͡Ί
    import * as THREE from 'three';
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
    const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
    document.getElementById('screen').appendChild(renderer.domElement);
    scene camera
    γʔϯͱΧϝϥ͕Ͱ͖Δ
    ͜ΕΛ DBOWBTͱͯ͠ %0.΁௥Ճ

    View Slide

  26. ΦϒδΣΫτΛੜ੒͢Δ
    const geometry = new THREE.BoxGeometry(1, 1, 1);
    const material = new THREE.MeshBasicMaterial({ color: 0x000000 });
    const cube = new THREE.Mesh(geometry, material);
    scene.add(cube);
    ཱํମ͕ઃஔ͞Εͨ
    scene
    cube

    View Slide

  27. ϏδϡΞϥΠβʔͷத਎
    ٖࣅίʔυ
    const rectShape = new THREE.Shape();
    const OFFSET = 円の中心から付け根の距離;
    rectShape.moveTo(
    OFFSET * cos(角度) + (幅 / 2) * cos(角度 + π / 2), // X座標
    OFFSET * sin(角度) + (幅 / 2) * sin(角度 + π / 2) // Y座標
    );
    rectShape.lineTo(
    OFFSET * cos(角度) + (幅 / 2) * cos(角度 - π / 2),
    OFFSET * sin(角度) + (幅 / 2) * sin(角度 - π / 2)
    );
    rectShape.lineTo(
    振幅 * cos(角度) + (幅 / 2) * cos(角度 + π / 2),
    振幅 * sin(角度) + (幅 / 2) * sin(角度 + π / 2)
    );
    rectShape.lineTo(
    振幅 * cos(角度) + (幅 / 2) * cos(角度 - π / 2),
    振幅 * sin(角度) + (幅 / 2) * sin(角度 - π / 2)
    );

    OFFSET→

    View Slide

  28. ϏδϡΞϥΠβʔͷத਎
    ٖࣅίʔυ
    const rectShape = new THREE.Shape();
    const OFFSET = 円の中心から付け根の距離;
    rectShape.moveTo(
    OFFSET * cos(角度) + (幅 / 2) * cos(角度 + π / 2), // X座標
    OFFSET * sin(角度) + (幅 / 2) * sin(角度 + π / 2) // Y座標
    );
    rectShape.lineTo(
    OFFSET * cos(角度) + (幅 / 2) * cos(角度 - π / 2),
    OFFSET * sin(角度) + (幅 / 2) * sin(角度 - π / 2)
    );
    rectShape.lineTo(
    振幅 * cos(角度) + (幅 / 2) * cos(角度 + π / 2),
    振幅 * sin(角度) + (幅 / 2) * sin(角度 + π / 2)
    );
    rectShape.lineTo(
    振幅 * cos(角度) + (幅 / 2) * cos(角度 - π / 2),
    振幅 * sin(角度) + (幅 / 2) * sin(角度 - π / 2)
    );

    OFFSET→
    ͜͜ͷৼ෯̍ຊ

    View Slide

  29. ը໘ͷస׵
    IUUQTZPVUVCF)QOVR/&:

    View Slide

  30. ը໘ͷస׵
    5ISFFKT DBOWBT
    3FBDUKT $PNQPOFOUT
    5ISFFKT DBOWBT ͕͜͜
    τϥϯδγϣϯ
    ͍ͯ͠Δ

    View Slide

  31. ը໘ͷస׵
    const material = new THREE.ShaderMaterial({ ... });
    const curtain = new THREE.Mesh(geometry, material);
    ShaderMaterial Λ࢖͏ͱɺ(-4-͕ॻ͚Δ

    View Slide

  32. ը໘ͷస׵
    uniform float mixRatio;
    uniform sampler2D tDiffuse1;
    uniform sampler2D tDiffuse2;
    varying vec2 vUv;
    void main() {
    vec4 tex1 = texture2D(tDiffuse1, vUv);
    vec4 tex2 = texture2D(tDiffuse2, vUv);
    gl_FragColor = mix(tex2, tex1, mixRatio);
    }
    const material = new THREE.ShaderMaterial({ });
    material.uniforms.mixRatio.value = transition;
    mixRatio
    ΛมԽͤ͞Δ͜ͱͰ
    ̎ͭͷγʔϯΛ੾Γସ͑Δ

    View Slide

  33. QPTUQSPDFTTJOH
    TDFOFʹඳը͞ΕͯΔϞϊͨͪશͯʹӨڹΛ༩͑ΔΤϑΣΫτ
    IUUQTZPVUVCF%N:[6BHPT

    View Slide

  34. QPTUQSPDFTTJOH 5ISFFKT

    import { EffectComposer, RenderPass, EffectPass, GlitchEffect } from 'postprocessing';
    const composer = new EffectComposer(renderer);
    composer.addPass(new RenderPass(scene, camera));
    const glitchEffect = new GlitchEffect();
    const glitchPass = new EffectPass(camera, glitchEffect);
    glitchPass.renderToScreen = false;
    composer.addPass(glitchPass);
    ͜ΕͰɺ͹Γ͹Γ͢Δԋग़͕ SFOEFSFSશମʹ͔͔Δ

    View Slide

  35. ΞϯνΤΠϦΞεॲཧ
    import { EffectPass, SMAAEffect } from 'postprocessing';
    const smaaEffect = new SMAAEffect();
    smaaEffect.setEdgeDetectionThreshold(0.08);
    const smaaPass = new EffectPass(camera, smaaEffect);
    smaaPass.renderToScreen = false;
    composer.addPass(smaaPass);
    SMAAEffect Λ࢖͏͜ͱͰ SFOEFSFSશମʹΞϯνΤΠϦΞε͕͔͔Δ

    View Slide

  36. දݱʗମݧΛߟ͑Δ

    View Slide

  37. ԕ͘ͳ͍ະདྷͰ͸
    8FCͷήʔϜ 18"
    ͰΞϓϦฒΈʹ༡΂Δ͸ͣ

    View Slide

  38. ΞϓϦͱൺ΂ͯͷϝϦοτ
    ɾ63-Λ౿Ί͹༡΂Δ
    ɾ8FC͍ͬͯ͏Πϯελϯτੑ
    ɾγΣΞͷ͠΍͢͞
    ͜ΕΛ׆͔ͯ͠ɺ༡ͼํ΋ 1SPHSFTTJWFʹͪ͠Ό͑͹

    View Slide

  39. ήʔϜηϯλʔͬͯͲ͏͍͏࣌ʹߦ͘ʁ
    Ո͡ΌͰ͖ͳ͍༡ͼํΛ͍ͨ࣌͠
    ͨͱ͑͹
    ݸਓͷ੠

    View Slide

  40. ࣗ෼Ͱଗ͑ΒΕͳ͍ػࡐ͕͋Δͱ͜ΖͰ༡Ϳ

    View Slide

  41. ྟ৔ײͨͬ΀ΓͳεϐʔΧʔ
    ͕͋ͬͨΓ
    େഭྗͷϏδϡΞϥΠβʔ͕͋ͬͨΓ
    ը૾Ҿ༻ɿ %+."9 NPTFT

    View Slide

  42. ໘ന͔ͬͨήʔϜΛ 18"Ͱ࣋ͪؼΔ

    View Slide

  43. %&.0
    %+ϛΩαʔͷ .*%*௨৴Λ #MVFUPPUIʹͷͤͯ
    8FCͰड͚औͬͯΤϑΣΫλʔΛ͔͚ͯΈΑ͏
    IUUQTZPVUVCFWR/T/P':

    View Slide

  44. 8FC#MVFUPPUI
    const device = await navigator.bluetooth.requestDevice({
    filters: [{ services: [UUID.SERVICE] }],
    });
    const server = await device.gatt.connect();
    const service = await server.getPrimaryService(UUID.SERVICE);
    const characteristic = await service.getCharacteristic(UUID.CHARACTERISTIC);
    await characteristic.startNotifications();
    const UUID = {
    SERVICE: '03b80e5a-ede8-4b33-a751-6ce34ec4c700‘,
    CHARACTERISTIC: '7772e5db-3868-4112-a1a9-f2669d106bf3',
    };
    .*%*σόΠεͷ 66*%

    View Slide

  45. .*%*#MVFUPPUI
    navigator.requestMIDIAccess({ sysex: true })
    .then(onMIDISuccess, onMIDIFailure);
    ͦͷͨΊ
    .*%*ͷΞΫηε΋΋Β͓ͬͯ͘
    ը૾Ҿ༻ɿ"NB[PODPN
    #MVFUPPUIΛ௨ͯ͠
    .*%*ͷ௨৴͕Ͱ͖ΔΑ͏ʹͳΔϞϊ
    .*%*ϙʔτ΍ 64#ϙʔτ͕ͳ͍
    σόΠεͰ΋ .*%*௨৴͕Ͱ͖Δ

    View Slide

  46. ΤϑΣΫλʔΛૢ࡞͢Δ
    characteristic.addEventListener(
    'characteristicvaluechanged‘,
    (event) => {
    const data = event.target.value;
    てきとうに処理...
    }
    )
    biquad
    Filter
    Node
    ૢ࡞
    .*%*͸ $-0$,γάφϧ͕͋ΔͨΊ
    characteristicvaluechanged ͸ΊͬͪΌൃՐ͢Δ
    ஫ҙ

    View Slide

  47. 8FC"TTFNCMZ8FC8PSLFS
    8FC$SZQUP"1*
    8FC"TTFNCMZͱ 8FC8PSLFS

    View Slide

  48. 8FC"TTFNCMZ

    View Slide

  49. 8FC"TTFNCMZ
    ϒϥ΢β༻ͷόΠφϦίʔυΛ༻ҙͯ͠
    ͦΕΛ +BWB4DSJQUଆ͔Βಈ͔ٕ͢ज़
    XBTN

    View Slide

  50. XBTN ͷ࡞Γํ
    $$--7.
    3VTU wasm-pack
    (P
    ,PUMJO/BUJWF
    ͜ΕΒͷࢿ࢈Λ 8FCʹ࣋ͪࠐΉ͜ͱ͕Ͱ͖Δ

    View Slide

  51. FNTDSJQUFO
    % emcc -O3 file.c -o file.wasm
    ୯ҰϑΝΠϧʹରͯ͠
    % ./emconfigure ./configure
    % ./emmake make
    % emcc -O3 project.bc -o project.wasm
    ϓϩδΣΫτʹରͯ͠
    -O3 ͸ϦϦʔεϏϧυʹదͨ͠࠷దԽ
    େจࣈͷΦʔͱ
    θϩ͡Όͳ͍Α

    View Slide

  52. $ˠ XBTN ͷಓͷΓ
    #include
    EMSCRIPTEN_KEEPALIVE
    int add(int x, int y) {
    return x + y;
    }
    EMSCRIPTEN_KEEPALIVE
    ࢀর͕ͳ͍ؔ਺͕࠷దԽͰ
    ফ͞Εͯ͠·͏
    Λ͚ͭͳ͍ͱɺ
    % emcc -O3 add.c -o add.wasm
    add.c
    ͬͦ͘͞Ͱ͖ͨ

    View Slide

  53. +BWB4DSJQUͰಈ͔͢
    const { instance } = await WebAssembly.instantiateStreaming(fetch('add.wasm'), {});
    const result = instance.exports.add(1, 1);
    console.log(result); // -> 2
    WebAssembly ʹ͸ଞͷಡΈࠐΈํ΋༻ҙ͞Ε͍ͯΔ
    instantiateStreaming ͕Ұ൪ޮ཰త
    ͚Ͳ

    View Slide

  54. ݟ͔ͭΒͳ͍ NPEVMF
    ͍ΖΜͳ NPEVMF͕ͳ͍ΑͱݴΘΕΔ
    ελϯυΞϩϯͰಈ͔ͳ͍ܗࣜͰ XBTN ʹίϯύΠϧ͖ͯͯ͠Δ
    Ͱ΋ɺͦͬͪͷ΄͏͕ܰྔͩ͠ແବ͕ͳ͍

    View Slide

  55. ݟ͔ͭΒͳ͍ NPEVMF
    await WebAssembly.instantiateStreaming(fetch('add.wasm'), imports);
    ͖ͬ͞͝·͔͍ͯͨ͠ୈҾ਺Λ͔ͭͬͯɺ
    ͦΕͧΕͷ NPEVMFΛఆ͍ٛͯ͘͠ඞཁ͕͋Δ
    const imports = {
    env: {
    __memory_base: 0,
    memory: new WebAssembly.Memory({
    initial: 512
    }),
    wasi_snapshot_preview1: (args) => {
    console.log(args);
    }
    }
    };
    ͜Μͳ͔Μ͡

    View Slide

  56. XBTN ͔Β JNQPSUͨؔ͠਺
    console.log('instance.exports.add →', instance.exports.add);
    OBUJWFDPEFʹͳ͍ͬͯΔ

    View Slide

  57. "TTFNCMZ4DSJQU
    export function add(a: i32, b: i32): i32 {
    return a + b;
    }
    5ZQF4DSJQUͷײ֮Ͱॻ͚Δ
    % npm install --save-dev assemblyscript
    % npx asc add.ts -b add.wasm -O3
    ɾͪΐͬͱ +4ͷॲཧૣ͍ͨ͘͠ͱ͜Ζ͕͋Δ
    ɾܭࢉΛଟ͘ͷ৔ॴͰ࢖͍ճ͢
    $$ͱ͔͸ա৒͚ͩͲ
    ͜͏͍͏ͱ͖ʹศར

    View Slide

  58. 8FC8PSLFS
    8FC8PSLFS

    View Slide

  59. 8FC8PSLFS
    +BWB4DSJQUΛϝΠϯεϨου͔Β֎ͯ͠ɺ
    όοΫάϥ΢ϯυʹ͢Δٕज़
    +BWB4DSJQU
    ϝΠϯεϨου
    8PSLFS
    εϨου
    ॏ͍ॲཧ͸
    ϝΠϯεϨουΛࢭΊͯ͠·͏
    8PSLFSʹ·͔ͤͯ
    ऴΘͬͨΒڭ͑ͯ΋Β͏
    ݟͯͷ௨Γ
    ॏ͍ॲཧ

    View Slide

  60. 8FC8PSLFSΛ૊ΈࠐΉ
    const worker = new Worker('worker.js');
    ͜ͷ࣌఺͔Β ͸ಈ͖࢝ΊΔ
    worker.js
    8PSLFSࣗମ͸ී௨ͷ +BWB4DSJQU
    ͨͩɺσʔλͷड͚౉͠͸ QPTU.FTTBHF Ͱߦ͏
    worker.postMessage('Hello from client');

    View Slide

  61. σʔλͷड͚౉͠
    const worker = new Worker('worker.js');
    worker.postMessage('Hello from client');
    worker.addEventListener('message', (message) => {
    console.log(message.data); // -> 'Hello from worker'
    });
    DMJFOUKT
    self.postMessage('Hello from worker');
    self.addEventListener('message', (message) => {
    console.log(message.data); // -> 'Hello from client'
    });
    XPSLFSKT
    self ͸ͳͯ͘΋ಈ͘
    const worker: Worker = self;
    Ͱ΋ඍົʹܕ͕ҧ͏͔Β 5ZQF4DSJQUͳΒɺ ͱͯ͠΋Α͍

    View Slide

  62. σʔλͷड͚౉͠ͷࠔΔͱ͜Ζ
    ී௨ʹ QPTU.FTTBHF ͰσʔλૹΔͱίϐʔ͞ΕΔ
    ॏ͍ॲཧ͕Ͱ͖ΔͬͯݴͬͯΔ͚Ͳ
    .# ͘Β͍ͷಈըϑΝΠϧͳΜͯૹͬͨΒɺ
    ίϐʔ͞Εͯ (# ΋ϝϞϦΛͻͬഭͯ͠͠·͏
    5SBOTGFSBCMF0CKFDUΛ࢖͏ʂ

    View Slide

  63. 5SBOTGFSBCMF0CKFDU
    const video = new Uint8Array(500 * 1024 * 1024);
    worker.postMessage({ type: 'SEND_VIDEO', video }, [video.buffer]);
    QPTU.FTTBHF ͷୈҾ਺ʹ "SSBZͰ
    "SSBZ#VGGFS ͔ .FTTBHF1PSU ͔ *NBHF#JUNBQ Λ౉͢
    8PSLFS
    DMJFOU
    video

    View Slide

  64. 5SBOTGFSBCMF0CKFDU
    const video = new Uint8Array(500 * 1024 * 1024);
    worker.postMessage({ type: 'SEND_VIDEO', video }, [video.buffer]);
    QPTU.FTTBHF ͷୈҾ਺ʹ "SSBZͰ
    "SSBZ#VGGFS ͔ .FTTBHF1PSU ͔ *NBHF#JUNBQ Λ౉͢
    8PSLFS
    DMJFOU
    video
    video ͸ DMJFOU͔Βॴ༗ݖ͕ 8PSLFSʹҠΔ
    QPTU.FTTBHF ޙ DMJFOUͰࢀরͰ͖ͳ͍

    View Slide

  65. ଎͞ͷҧ͍ DMJFOUଆ
    worker.postMessage({ type: 'START_TIMER' });
    const video = new Uint8Array(500 * 1024 * 1024);
    worker.postMessage({ type: 'SEND_VIDEO', video });
    DMJFOUKTʢͦͷ··ૹΔʣ
    worker.postMessage({ type: 'START_TIMER' });
    const video = new Uint8Array(500 * 1024 * 1024);
    worker.postMessage({ type: 'SEND_VIDEO', video }, [video.buffer]);
    DMJFOUKTʢ5SBOTGFSBCMF0CKFDUʣ

    View Slide

  66. ଎͞ͷҧ͍ XPSLFSଆ
    let startTime = null;
    self.addEventListener('message', (message) => {
    const { type, video } = message.data;
    switch (type) {
    case 'START_TIMER':
    startTime = performance.now();
    break;
    case 'SEND_VIDEO':
    console.log(performance.now() - startTime);
    break;
    }
    });
    XPSLFSKT

    View Slide

  67. ଎͞ͷҧ͍ ݁Ռ
    DMJFOUKTʢͦͷ··ૹΔʣ
    DMJFOUKTʢ5SBOTGFSBCMF0CKFDUʣ
    866.2800000165589
    0.9349999600090086
    ര଎
    ͍ͭͰʹলϝϞϦ

    View Slide

  68. දݱʗମݧΛߟ͑Δ

    View Slide

  69. ը૾ʗಈըΞϓϦ͕ΊͬͪΌߴػೳʹͳͬͯΔ
    ࡱͬͨΒ͙͢ʹ $MPVEʹ͕͋ͬͯ
    ୭͕ࣸͬͯΔ͔ɺͲ͜ʹ͍͔ͨ·ͱΊͯ͘ΕΔ
    ৔ॴผ
    ਓ෺ผ
    ͱͯ΋΂ΜΓʂ

    View Slide

  70. ҰํͰ
    ࣗ෼ͷࣸਅɾಈըΛʠͱ΍͔͘ʡ͞Εͨ͘ͳ͍ਓ͍ͩͬͯΔ
    ʮͰ΋ɺ$MPVEʹ͋͛ΕͨΒɺखݩͷ༰ྔݮΒͤΔ͠ͳʔʜʯ
    ʠͱ΍͔͘ʡ͞Εͨ͘ͳ͍ਓɿ
    ʮϓϥΠϕʔτΫϥ΢υΈ͍ͨͷ࢖͑͹ʁʯ
    ఢɿ
    ʮͦ͏͍͏ͷͬͯຊ౰ʹ๣ௌ͞Εͯͳ͍͔ͱ͔ɺ
    ෼ੳʹ࢖ΘΕͯͳ͍͔ͱ͔Θ͔Μͳ͘ͳ͍ʁʁʯ
    ʠͱ΍͔͘ʡ͞Εͨ͘ͳ͍ਓɿ

    View Slide

  71. ࣗ෼ͰʢϩʔΧϧͰʣ΍Δ͔͠ͳ͍͡ΌΜʂ
    ը૾͸αϜωΠϧ੾Γग़͠Λͯ͠
    ಈը͸Τϯίʔυ αϜωΠϧ੾Γग़͠Λͯ͠
    ҉߸Խͨ͠΋ͷ͚ͩΛ֎ʹग़͢Α͏ʹ͠Α͏
    ΍Δ͔͠ͳ͍
    ʢʠͱ΍͔͘ʡ͞Εͨ͘ͳ͍ਓஊ ʣ

    View Slide

  72. ΍Δͧ
    ҉߸Խ͞Εͯͳ͍σʔλΛͲ͔͜ʹૹΔͳΜͯͰ͖ͳ͍ʂ
    ը૾ͷαϜωΠϧԽ
    *NBHF.BHJDL ͕࢖͑ͦ͏
    ಈըͷΤϯίʔυ
    ''.1&(͕͍͍ͳ

    View Slide

  73. <br/>import * as Magick from 'https://knicknic.github.io/wasm-imagemagick/magickApi.js';<br/>const call = async function() {<br/>const fetchedSourceImage = await fetch('sample.png‘);<br/>const arrayBuffer = await fetchedSourceImage.arrayBuffer();<br/>const sourceBytes = new Uint8Array(arrayBuffer);<br/>const files = [{ name: 'srcFile.png', content: sourceBytes }];<br/>const command = ['convert', 'srcFile.png', '-rotate', '90', '-resize', '200%', 'out.png'];<br/>const processedFiles = await Magick.Call(files, command);<br/>const firstOutputImage = processedFiles[0];<br/>document.getElementById('rotatedImage').src = URL.createObjectURL(firstOutputImage['blob']);<br/>console.log('created image ' + firstOutputImage['name']);<br/>};<br/>call();<br/>
    XBTNJNBHFNBHJDL
    *NBHF.BHJDL Λ XBTN Ͱಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ

    View Slide

  74. <br/>import * as Magick from 'https://knicknic.github.io/wasm-imagemagick/magickApi.js‘;<br/>const call = async function() {<br/>const fetchedSourceImage = await fetch('sample.png‘);<br/>const arrayBuffer = await fetchedSourceImage.arrayBuffer();<br/>const sourceBytes = new Uint8Array(arrayBuffer);<br/>const files = [{ name: 'srcFile.png', content: sourceBytes }];<br/>const command = ['convert', 'srcFile.png', '-rotate', '90', '-resize', '200%', 'out.png'];<br/>const processedFiles = await Magick.Call(files, command);<br/>const firstOutputImage = processedFiles[0];<br/>document.getElementById('rotatedImage').src = URL.createObjectURL(firstOutputImage['blob']);<br/>console.log('created image ' + firstOutputImage['name']);<br/>};<br/>call();<br/>
    XBTNJNBHFNBHJDL
    *NBHF.BHJDL Λ XBTN Ͱಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ
    ͜Εܥɺσʔλ͸͍͍ͩͨ "SSBZ#VGGFS Ͱ΍ΓͱΓ͢Δ

    View Slide

  75. <br/>import * as Magick from 'https://knicknic.github.io/wasm-imagemagick/magickApi.js‘;<br/>const call = async function() {<br/>const fetchedSourceImage = await fetch('sample.png‘);<br/>const arrayBuffer = await fetchedSourceImage.arrayBuffer();<br/>const sourceBytes = new Uint8Array(arrayBuffer);<br/>const files = [{ name: 'srcFile.png', content: sourceBytes }];<br/>const command = ['convert', 'srcFile.png', '-rotate', '90', '-resize', '200%', 'out.png'];<br/>const processedFiles = await Magick.Call(files, command);<br/>const firstOutputImage = processedFiles[0];<br/>document.getElementById('rotatedImage').src = URL.createObjectURL(firstOutputImage['blob']);<br/>console.log('created image ' + firstOutputImage['name']);<br/>};<br/>call();<br/>
    XBTNJNBHFNBHJDL
    *NBHF.BHJDL Λ XBTN Ͱಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ
    *NBHF.BHJDL Ͱ࢖͑ΔίϚϯυ͸͍͍ͩͨಈ͘

    View Slide

  76. const ffmpeg = new Worker('/workers/ffmpeg-worker-webm.js');
    const call = async () => {
    const fetchedSourceMov = await fetch('sample.mov');
    const arrayBuffer = await fetchedSourceMov.arrayBuffer();
    const item = new Uint8Array(arrayBuffer);
    await new Promise((resolve, reject) => {
    ffmpeg.postMessage({
    type: 'run',
    MEMFS: [{ name: 'input', data: item }],
    arguments: ['-ss', '2', '-i', 'input', '-f', 'image2', '-vframes', '1', 'out.jpg']
    });
    });
    };
    call();
    GGNQFHKT
    GGNQFH Λ XBTN ͱ XPSLFSͰಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ

    View Slide

  77. const ffmpeg = new Worker('/workers/ffmpeg-worker-webm.js');
    const call = async () => {
    const fetchedSourceMov = await fetch('sample.mov');
    const arrayBuffer = await fetchedSourceMov.arrayBuffer();
    const item = new Uint8Array(arrayBuffer);
    await new Promise((resolve, reject) => {
    ffmpeg.postMessage({
    type: 'run',
    MEMFS: [{ name: 'input', data: item }],
    arguments: ['-ss', '2', '-i', 'input', '-f', 'image2', '-vframes', '1', 'out.jpg']
    });
    });
    };
    call();
    GGNQFHKT
    GGNQFH Λ XBTN ͱ XPSLFSͰಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ
    ઌʹशͬͯ "SSBZ#VGGFS ʹͯ͠Δ͚ͩ

    View Slide

  78. const ffmpeg = new Worker('/workers/ffmpeg-worker-webm.js');
    const call = async () => {
    const fetchedSourceMov = await fetch('sample.mov');
    const arrayBuffer = await fetchedSourceMov.arrayBuffer();
    const item = new Uint8Array(arrayBuffer);
    await new Promise((resolve, reject) => {
    ffmpeg.postMessage({
    type: 'run',
    MEMFS: [{ name: 'input', data: item }],
    arguments: ['-ss', '2', '-i', 'input', '-f', 'image2', '-vframes', '1', 'out.jpg']
    });
    });
    };
    call();
    GGNQFHKT
    GGNQFH Λ XBTN ͱ XPSLFSͰಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ
    .&.'4
    8FC"TTFNCMZͰ࣋ͬͯΔϑΝΠϧγεςϜͷ̍ͭ
    ϝϞϦۭؒΛ࢖ͬͨϑΝΠϧγεςϜ

    View Slide

  79. ffmpeg.onmessage = (event) => {
    switch (event.data.type) {
    case 'done':
    if (msg.data.MEMFS[0]) {
    const textDecoder = new TextDecoder();
    const thumbUnit8 = msg.data.MEMFS[0].data;
    const img = textDecoder.decode(thumbUnit8);
    document.getElementById('thumb').src = img;
    }
    break;
    }
    };
    GGNQFHKTʢσʔλͷडऔʣ

    View Slide

  80. ffmpeg.onmessage = (event) => {
    switch (event.data.type) {
    case 'done':
    if (msg.data.MEMFS[0]) {
    const textDecoder = new TextDecoder();
    const thumbUnit8 = msg.data.MEMFS[0].data;
    const img = textDecoder.decode(thumbUnit8);
    document.getElementById('thumb').src = img;
    }
    break;
    }
    };
    GGNQFHKTʢσʔλͷडऔʣ
    #ZUF4USFBNΛ 4USJOHʹ͢ΔͨΊ

    View Slide

  81. ͋ͱ͸҉߸Խ
    αʔόʔΛ࢖Θͣʹɺ
    +BWB4DSJQU͚ͩͰαϜωΠϧɺΤϯίʔυ͕Ͱ͖ͦ͏ʂ
    ҉߸Խ΋ࣗલͰ΍͔ͬͯΒαʔόʔʹσʔλΛૹΖ͏
    XBTN ͨͪ

    View Slide

  82. 8FC$SZQUP"1*

    View Slide

  83. 8FC$SZQUP"1*
    ·ͣɺݤΛͭ͘Δ
    const cryptoKey: CryptoKey = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 },
    true,
    ['encrypt', 'decrypt']
    );
    const jwk = await crypto.subtle.exportKey('jwk', cryptoKey);
    ࡞ΓऴΘͬͨΒ FYQPSUͯ͠
    *OEFYFE%# ͱ͔ʹอଘ͓ͯ͘͠ͱྑ͍

    View Slide

  84. 8FC$SZQUP"1*
    ·ͣɺݤΛͭ͘Δ
    const cryptoKey: CryptoKey = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 },
    true,
    ['encrypt', 'decrypt']
    );
    ҉߸Խར༻Ϟʔυ
    const jwk = await crypto.subtle.exportKey('jwk', cryptoKey);
    ࡞ΓऴΘͬͨΒ FYQPSUͯ͠
    *OEFYFE%# ͱ͔ʹอଘ͓ͯ͘͠ͱྑ͍

    View Slide

  85. 8FC$SZQUP"1*
    ·ͣɺݤΛͭ͘Δ
    const cryptoKey: CryptoKey = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 },
    true,
    ['encrypt', 'decrypt']
    );
    ݤ͕ΤΫεϙʔτՄೳ͔Ͳ͏͔
    ҉߸Խར༻Ϟʔυ
    const jwk = await crypto.subtle.exportKey('jwk', cryptoKey);
    ࡞ΓऴΘͬͨΒ FYQPSUͯ͠
    *OEFYFE%# ͱ͔ʹอଘ͓ͯ͘͠ͱྑ͍

    View Slide

  86. 8FC$SZQUP"1*
    ·ͣɺݤΛͭ͘Δ
    const cryptoKey: CryptoKey = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 },
    true,
    ['encrypt', 'decrypt']
    );
    ҉߸Խͱ෮߸Խ͕ڐՄ͞Εͨݤ
    ݤ͕ΤΫεϙʔτՄೳ͔Ͳ͏͔
    ҉߸Խར༻Ϟʔυ
    const jwk = await crypto.subtle.exportKey('jwk', cryptoKey);
    ࡞ΓऴΘͬͨΒ FYQPSUͯ͠
    *OEFYFE%# ͱ͔ʹอଘ͓ͯ͘͠ͱྑ͍

    View Slide

  87. 'JMFΛ҉߸Խ͢Δ·Ͱ
    const textEncoder = new TextEncoder();
    const reader = new FileReaderSync();
    const blob = new Blob([file as File], { type: file.type });
    const stringItem: string = reader.readAsDataURL(blob);
    const encodedBlob: Uint8Array = textEncoder.encode(stringItem);
    const iv: Uint8Array = crypto.getRandomValues(new Uint8Array(12));
    const cryptedBuffer: ArrayBuffer = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    cryptoKey,
    encodedBlob
    );
    const cryptedItem = new Uint8Array(cryptedBuffer);

    View Slide

  88. 'JMFΛ҉߸Խ͢Δ·Ͱ
    const textEncoder = new TextEncoder();
    const reader = new FileReaderSync();
    const blob = new Blob([file as File], { type: file.type });
    const stringItem: string = reader.readAsDataURL(blob);
    const encodedBlob: Uint8Array = textEncoder.encode(stringItem);
    const iv: Uint8Array = crypto.getRandomValues(new Uint8Array(12));
    const cryptedBuffer: ArrayBuffer = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    cryptoKey,
    encodedBlob
    );
    const cryptedItem = new Uint8Array(cryptedBuffer);
    ΍΍͍͚͜͠Ͳ
    8FC$SZQUP"1*ʹ͔͚ΕΔܗʹ͍ͯ͠Δ͚ͩ

    View Slide

  89. 'JMFΛ҉߸Խ͢Δ·Ͱ
    const textEncoder = new TextEncoder();
    const reader = new FileReaderSync();
    const blob = new Blob([file as File], { type: file.type });
    const stringItem: string = reader.readAsDataURL(blob);
    const encodedBlob: Uint8Array = textEncoder.encode(stringItem);
    const iv: Uint8Array = crypto.getRandomValues(new Uint8Array(12));
    const cryptedBuffer: ArrayBuffer = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    cryptoKey,
    encodedBlob
    );
    const cryptedItem = new Uint8Array(cryptedBuffer);
    ͕͜͜҉߸ԽΛͯ͠Δͱ͜Ζ

    View Slide

  90. 'JMFΛ҉߸Խ͢Δ·Ͱ
    const textEncoder = new TextEncoder();
    const reader = new FileReaderSync();
    const blob = new Blob([file as File], { type: file.type });
    const stringItem: string = reader.readAsDataURL(blob);
    const encodedBlob: Uint8Array = textEncoder.encode(stringItem);
    const iv: Uint8Array = crypto.getRandomValues(new Uint8Array(12));
    const cryptedBuffer: ArrayBuffer = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    cryptoKey,
    encodedBlob
    );
    const cryptedItem = new Uint8Array(cryptedBuffer);
    ॳظԽʢΠχγϟϧʣϕΫτϧ
    ಉ͡ΞΠςϜΛಉ͡ݤͰ҉߸Խͯ͠΋
    ݁Ռ͕มΘΔΑ͏ʹ͢ΔͨΊʹඞཁ

    View Slide

  91. ͪͳΈʹ෮߸Խ
    const textDecoder = new TextDecoder();
    const rawBuffer : ArrayBuffer = await crypto.subtle.decrypt(
    { name: 'AES-GCM', iv },
    cryptoKey,
    cryptedItem
    );
    const rawItem = new Uint8Array(rawBuffer);
    const thumb: string = textDecoder.decode(rawItem);
    crypto.subtle.decrypt
    Λ࢖͔ͬͯΒ҉߸Խͷ൓ରͷॱংͰ TUSJOH·Ͱ͍࣋ͬͯ͘

    View Slide

  92. %&.0
    Ͱ౉͞ΕͨࣸਅɾಈըΛ
    ੈքʹ͹Εͳ͍Α͏ʹखݩͰॲཧ͔ͯ͠Β
    ҉߸ԽΛͯ͠Ξοϓϩʔυͯ͠ΈΑ͏
    IUUQTZPVUVCFPF9R#K8,:
    IUUQTZPVUVCFCR3RIU,1*

    View Slide

  93. 1VTI"1*͍··Ͱͷ "1*
    1VTI"1*

    View Slide

  94. 1VTI"1*
    ΞϓϦͷΑ͏ʹ௨஌͕ग़ͤΔ࢓૊Έ
    ͍͔ͭ͘ͷ࣮૷ͷ࢓ํ͕͋Δ͚ΕͲ
    'JSFCBTF$MPVE.FTTBHJOH͕؆୯ͰԠ༻·ͰͰ͖Δ
    ˞"OESPJEͷΈରԠ

    View Slide

  95. 'JSFCBTF$MPVE.FTTBHJOH
    {
    name: 'app-name‘,
    short_name: 'app-shrt‘,
    ...,
    gcm_sender_id: 'xxxxxxxxxxxx'
    };
    'JSFCBTFͰϓϩδΣΫτΛ࡞੒
    gcm_sender_idΛݟ͚ͭͯ NBOJGFTUKTPO ʹ௥Ճ
    NBOJGFTUKTPO

    )5.-ʹ௥Ճ

    View Slide

  96. 4FSWJDF8PSLFSͷ௥Ճ
    4FSWJDF8PSLFSɿϖʔδΛดͯ͡΋ಈ͍ͯ͘ΕΔ 8PSLFS
    navigator.serviceWorker.register(‘/service-worker.js')
    navigator ͔Β௥Ճ͢Δ
    service-worker.js
    ผͷϥΠϑαΠΫϧͰ
    ಈ͖ग़͢
    XJOEPX

    View Slide

  97. 'JSFCBTF͔Βͷ௨஌Λड͚Δ
    ͜ΕͰ 8FCϖʔδ͕
    όοΫάϥ΢ϯυʗλεΫΩϧঢ়ଶͰ΋௨஌͕ड͚औΕΔ
    import * as firebase from 'firebase/app';
    import 'firebase/messaging';
    const messaging = firebase.messaging();
    messaging.setBackgroundMessageHandler((payload) => {
    console.log(payload);
    });
    TFSWJDFXPSLFSKT

    View Slide

  98. 8FCϖʔδʹ͍Δͱ͖ͷ௨஌
    import * as firebase from 'firebase/app';
    import 'firebase/messaging';
    const messaging = firebase.messaging();
    messaging.onMessage((payload) => {
    console.log(payload);
    });
    Ͱ΋ड͚औΕΔ͚Ͳ
    addEventListener('push', () => {});
    'JSFCBTFͰߦ͘ͳΒ PO.FTTBHF Ͱ 0,
    DMJFOUKTʢ8PSLFS͡Όͳ͍ํͰʣ

    View Slide

  99. import * as firebase from 'firebase/app';
    import 'firebase/messaging';
    const messaging = firebase.messaging();
    const registration = await navigator.serviceWorker.register('/service-worker.js');
    try {
    await messaging.requestPermission();
    messaging.usePublicVapidKey(FIREBASE_PUBLIC_VAPID_KEY);
    messaging.useServiceWorker(registration);
    const token = await messaging.getToken();
    console.log(token);
    } catch (e) {
    console.log('rejected!');
    }
    ௨஌ͷڐՄͱ UPLFOͷऔಘ

    View Slide

  100. import * as firebase from 'firebase/app';
    import 'firebase/messaging';
    const messaging = firebase.messaging();
    const registration = await navigator.serviceWorker.register('/service-worker.js');
    try {
    await messaging.requestPermission();
    messaging.usePublicVapidKey(FIREBASE_PUBLIC_VAPID_KEY);
    messaging.useServiceWorker(registration);
    const token = await messaging.getToken();
    console.log(token);
    } catch (e) {
    console.log('rejected!');
    }
    ௨஌ͷڐՄͱ UPLFOͷऔಘ
    'JSFCBTFͷίϯιʔϧ͔Β࣋ͬͯ͜ΕΔ
    ௨஌ͷڐՄΛऔΓʹߦ͘
    ͜͜Ͱ UPLFOΛखʹೖΕΔ

    View Slide

  101. ௨஌ڐՄͷμϝͳྫ
    ϖʔδ͸͍͙ͬͯ͢ʹ௨஌ڐՄΛऔΖ͏ͱ͢Δ 8FCαΠτ
    ͱͯ΋ةݥ
    ͲΜͳίϯςϯπ͕ͦ͜ʹ͋Δ͔Θ͔Βͳ͍Ϣʔβʔ͕
    ڐՄ͢ΔϝϦοτ͕ͳ͍
    ͳΜͷؾͳ͠ʹɺϒϩοΫΛ͞ΕΔͱυϝΠϯͰڋ൱͞ΕΔͷͰɺ
    ೋ౓ͱϢʔβʔʹ௨஌ΛૹΔνϟϯε͕ͳ͘ͳΔʹͻͱ͍͠

    View Slide

  102. ഑ྀͨ͠ྫ
    ࣗલͷμΠΞϩάͰαϯυϘοΫεతʹଅ͢ͷ͕ྑ͍
    $MJDL
    Ϣʔβʔ͕௨஌Λ΄͍͠ͱײͦ͡͏ͳ৔໘Ͱ΍Δͱ͍͍ΑͶ
    ͓ؾʹೖΓͷϢʔβʔͷ৽ண͕ؾʹͳΓ·ͤΜ͔ʁ
    ΠϕϯτͷεέδϡʔϧΛ͓஌Βͤ͠·͢Α
    ͋ͳ͕ͨ޷͖ͦ͏ͳهࣄ͕ͰͨΒ͓ಧ͚ͯ͠ྑ͍ʁ ͨͱ͑͹͜Μͳ

    View Slide

  103. ಛఆͷϢʔβʔʹ௨஌͢Δ
    https://fcm.googleapis.com/v1/projects/{project-id}/messages:send
    ͜͜ʹ 1045͢Δ
    headers: {
    'content-type': 'application/json',
    authorization: `Bearer ${VALID_OAUTH_2_0_TOKEN}`
    },
    body: JSON.stringify({
    message: {
    token,
    notification: {
    title: 'これはタイトル',
    body: 'こっちは本文'
    }
    }
    })

    View Slide

  104. ಛఆͷϢʔβʔʹ௨஌͢Δ
    https://fcm.googleapis.com/v1/projects/{project-id}/messages:send
    ͜͜ʹ 1045͢Δ
    headers: {
    'content-type': 'application/json',
    authorization: `Bearer ${VALID_OAUTH_2_0_TOKEN}`
    },
    body: JSON.stringify({
    message: {
    token,
    notification: {
    title: 'これはタイトル',
    body: 'こっちは本文'
    }
    }
    })
    (PPHMF"1*ΫϥΠΞϯτϥΠϒϥϦͰ +85Λऔಘ

    View Slide

  105. ಛఆͷϢʔβʔʹ௨஌͢Δ
    https://fcm.googleapis.com/v1/projects/{project-id}/messages:send
    ͜͜ʹ 1045͢Δ
    headers: {
    'content-type': 'application/json',
    authorization: `Bearer ${VALID_OAUTH_2_0_TOKEN}`
    },
    body: JSON.stringify({
    message: {
    token,
    notification: {
    title: 'これはタイトル',
    body: 'こっちは本文'
    }
    }
    })
    (PPHMF"1*ΫϥΠΞϯτϥΠϒϥϦͰ +85Λऔಘ
    ͖ͬ͞ͷ UPLFO

    View Slide

  106. දݱʗମݧΛߟ͑Δ

    View Slide

  107. ͔͜͜Β͸ڠྗͯ͠΋Βͬͨௐࠪ݁Ռ΋దٓग़͍ͯ͘͠Α
    ͝ڠྗ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ

    View Slide

  108. ੈքதηϯαʔͰҲΕ͔͑ͬͯΔ
    Թ౓͕Θ͔Γ·͢
    ࣪౓Λײ͡·͢
    Ի͕ฉ͑͜·͢

    View Slide

  109. ੈքதηϯαʔͰҲΕ͔͑ͬͯΔ
    ۙ͘ͷ #MVFUPPUI͕ݟΕ·͢
    ໌Δ͞Λ൑ఆ͠·͢
    Ի͕ฉ͑͜·͢
    Ҡಈ଎౓͕Θ͔Γ·͢
    Χϝϥ͋Γ·͢
    ͍ͭͰʹɺ͍͍ͩͨΦϯϥΠϯ

    View Slide

  110. ͍͕ͭ͜൓ضΛ຋͢ͱාͦ͏
    ৐ͬऔΒΕΔͱ·͍ͣ

    View Slide

  111. 23ΛಡΈࠐΉ͚ͩͰ
    63-ʹδϟϯϓ
    service-worker
    Πϯετʔϧ͞ΕΔ
    ͋ͱ͸௨஌ڐՄ͑͞΋Β͑Ε͹
    λεΫΩϧ͞Εͯͯ΋ಈ͔ͤͦ͏
    1VTI"1*Λߟ͑Δͱ

    View Slide

  112. ಈ͔ͤͦ͏ʁ
    ௨஌ग़ͤΔ͚ͩ͡Όͳ͍ͷʁ
    ௨஌ϝοηʔδͱσʔλϝοηʔδ͕͋Δ
    ௨஌ϝοηʔδɿ
    ௨஌Λग़͚ͩ͢ͷϝοηʔδ
    σʔλϝοηʔδɿ
    σʔλΛड͚औͬͯ 4FSWJDF8PSLFS ಺Ͱॲཧ͢Δ

    View Slide

  113. ಈ͔ͤͦ͏ʁ
    ௨஌ग़ͤΔ͚ͩ͡Όͳ͍ͷʁ
    ௨஌ϝοηʔδͱσʔλϝοηʔδ͕͋Δ
    ௨஌ϝοηʔδɿ
    ௨஌Λग़͚ͩ͢ͷϝοηʔδ
    σʔλϝοηʔδɿ
    σʔλΛड͚औͬͯ 4FSWJDF8PSLFS ಺Ͱॲཧ͢Δ
    ʮΉΉʯ

    View Slide

  114. message: {
    token,
    data: {
    ...
    }
    }
    message: {
    token,
    notification: {
    title: 'これはタイトル‘,
    body: 'こっちは本文‘
    }
    }
    ௨஌ϝοηʔδ σʔλϝοηʔδ
    ௨஌Λग़͞ͳͯ͘΋ྑ͍
    4FSWJDF8PSLFS ͕ಈ࡞͢Δ

    View Slide

  115. 4FSWJDF8PSLFS ͰͰ͖Δ͜ͱ
    WorkerNavigator
    Navigator
    battery
    connection
    geolocation
    lock
    permissions
    serviceWorker
    storage
    xr
    vibrate
    clipboard
    usb
    bluetooth
    credentials
    :Battery Status API
    :Network Information API
    :Geolocation API
    :Web Lock API
    :Permission API
    :ServiceWorkerContainer
    :Storage API
    :Web XR Device API
    :Vibration API
    :Clipboard API
    :Web USB
    :Web Bluetooth
    :Credentials Container
    connection
    lock
    permissions
    storage
    :Network Information API
    :Web Lock API
    :Permission API
    :Storage API
    'FUDI"1*͕ಈ͘
    ͋ͱ

    View Slide

  116. 4FSWJDF8PSLFS ͰͰ͖Δ͜ͱ
    WorkerNavigator
    Navigator
    battery
    connection
    geolocation
    lock
    permissions
    serviceWorker
    storage
    xr
    vibrate
    clipboard
    usb
    bluetooth
    credentials
    :Battery Status API
    :Network Information API
    :Geolocation API
    :Web Lock API
    :Permission API
    :ServiceWorkerContainer
    :Storage API
    :Web XR Device API
    :Vibration API
    :Clipboard API
    :Web USB
    :Web Bluetooth
    :Credentials Container
    connection
    lock
    permissions
    storage
    :Network Information API
    :Web Lock API
    :Permission API
    :Storage API
    'FUDI"1*͕ಈ͘
    ͋ͱ
    ʮ͋ͬʯ

    View Slide

  117. %&.0
    ࢦఆͨ͠ 63-Λୟ͖ʹߦͬͯ΋Β͓
    IUUQTZPVUVCF)HR/XHIMTX

    View Slide

  118. #PUOFUͷΑ͏ͳৼΔ෣͍
    service-worker
    ߈ܸϓϩάϥϜೖΓ
    ߈ܸର৅
    63-Λ̍ͭ౿Ή͚ͩͰ
    #PUOFUʹͳΔةݥੑ
    %%04߈ܸʂ

    View Slide

  119. ΩϟϦΞճઢͱ 8*'*ซ༻Ͱ *1มΘͬͪΌͬͯ
    ϒϩοΩϯά΋Ή͍͔ͣ΋ʁ
    ௐࠪ݁Ռ Ұ೔ͷ͏ͪʹͲͷ͘Β͍ *1มΘΔʁ
    Ұਓ͋ͨΓ

    ೔Ͱ࢖༻͢Δ *1ΞυϨε
    Մ΋ͳ͘ෆՄ΋ͳ͘

    View Slide

  120. ϗϯτ͸ͦΜͳ৺഑͡Όͳ͍
    ͍͍ͩͨ͜Ε͕कͬͯ͘ΕΔ

    View Slide

  121. ྑ͍࢖ΘΕํ΋ߟ͍͑ͨ

    View Slide

  122. දݱʗମݧΛߟ͑Δ
    ͖͠Γͳ͓͠

    View Slide

  123. ΠέͯΔ 8FCΞϓϦ࡞͚ͬͨͲ
    ӡӦ͍ͯ͘͠ҡ࣋අ͕͖͍ͭ

    View Slide

  124. ΠέͯΔ 8FCΞϓϦ࡞͚ͬͨͲ
    ӡӦ͍ͯ͘͠ҡ࣋අ͕͖͍ͭ
    ޿ࠂ
    ޿ࠂ
    ޿ࠂ
    ޿ࠂ
    ޿ࠂ
    ޿ࠂ
    ޿ࠂ
    ޿ࠂ
    ޿ࠂ
    ޿ࠂ
    ޿ࠂ ޿ࠂ
    ޿ࠂ
    ޿ࠂ

    View Slide

  125. Ծ૝௨՟Λ۷ͬͯΈΔʁ
    $PJOIJWF Ͱѱ໊ΛͻΖΊͨʠϒϥ΢βϕʔεϚΠχϯάʡ
    ແڐՄͰ΍Δͷ͸μϝͳͷ͸΋ͪΖΜͷ͜ͱ
    ޿ࠂ͸ग़ͨ͘͠ͳ͍ ҡ࣋අ͸୲อ͍ͨ͠
    ͜͏͍͏৚݅ԼͰ͸ྑ͍ΞΠσΞͰ͸͋Δ

    View Slide

  126. Ͱ΋ڐՄΛऔͬͨ·ͱ΋ͳར༻Ͱ΋ 69ΘΔ͍͘ͳ͍ʁ
    8FCΞϓϦ࢖ͬͯΔ࠷தʹ
    ೤͘ͳΔ
    ి஑৯͏
    ॏ͍
    Ϣʔβʔ཭Εͦ͏

    View Slide

  127. ࠒ߹͍Λݟͯ΍Δ
    Ұ൪࢖ͬͯͳͯ͘ɺόοςϦʔ΋৺഑ͳͯ͘
    ΦϯϥΠϯͰ͔ͭߴ଎Ͱ઀ଓ͞ΕͯΔঢ়ଶ
    ॆి͞Εͯͯɺ8J'Jʹͭͳ͕ͬͯͳ͍ʁ

    View Slide

  128. 0
    5
    10
    15
    20
    25

    TMPXH H H H
    ຊ౰ͩΖ͔
    ௐࠪ݁Ռ Ұ೔தͱ͓ͯ͠ͷ୺຤ͷ଎౓ͬͯͲͷ͘Β͍ʁ
    ࣌ؒ
    ਓ਺

    View Slide

  129. 0
    5
    10
    15
    20
    25

    TMPXH H H H
    ຊ౰ͩΖ͔
    ௐࠪ݁Ռ Ұ೔தͱ͓ͯ͠ͷ୺຤ͷ଎౓ͬͯͲͷ͘Β͍ʁ
    ࣌ؒ
    ਓ਺
    ͍͍ͩͨɺ͍ͭ΋଎͍

    View Slide

  130. 0
    5
    10
    15
    20
    25

    TMPXH H H H
    ຊ౰ͩΖ͔
    ௐࠪ݁Ռ Ұ೔தͱ͓ͯ͠ͷ୺຤ͷ଎౓ͬͯͲͷ͘Β͍ʁ
    ࣌ؒ
    ਓ਺
    ͍ͯ͠ݴ͑͹
    ͜ͷ΁ΜࠞΜͰΔ

    View Slide

  131. 0
    5
    10
    15
    20
    25

    TMPXH H H H
    ຊ౰ͩΖ͔
    ௐࠪ݁Ռ Ұ೔தͱ͓ͯ͠ͷ୺຤ͷ଎౓ͬͯͲͷ͘Β͍ʁ
    ࣌ؒ
    ਓ਺
    ਂ໷͔Βૣே͸
    ͪΐͬͱۭ͍ͯΔʁ

    View Slide

  132. 3PVOE5SJQ5JNF









    3PVOE5SJQ5JNF
    SUU
    ࣌ࠁ

    View Slide

  133. 3PVOE5SJQ5JNF









    3PVOE5SJQ5JNF
    0
    5
    10
    15
    20
    25

    TMPXH H H H
    ҰͭલͷάϥϑͷԼͷํ
    SUU
    ࣌ࠁ

    View Slide

  134. ݴ͍͔ͨͬͨ͜ͱ͸
    ৸ͯͦ͏ͳ࣌ؒ͸֨ผ଎͍͸ͣʂ ͚ͩͬͨͲ
    ͍͍ͩͨ଎͍ͳΒɺ·͊ 0,Ͱ͠ΐ͏

    View Slide

  135. දݱʗମݧΛߟ͑Δ
    ̏ͭΊ

    View Slide

  136. ʮ͖ͬ͞ϩʔΧϧͰը૾ʗಈըΛ҉߸Խ͚ͨ͠Ͳɺ
    ΋͏ϩʔΧϧʹ࣋ͬͯΔͷ΋ෆ҆ʯ
    ʠͱ΍͔͘ʡ͞Εͨ͘ͳ͍ਓɿ
    ʮͩͬͯ 4FSWJDF8PSLFS͍ͬͯ͏ͷΈͨ͠ʯ
    ʮͳʹ͞ΕͯΔ͔Θ͔ͬͨ΋Μ͡Όͳ͍ΑͶʯ
    ʮͦ΋ͦ΋҆৺ͬͯͳʹΛ࣋ͬͯͦ͏͍͏;͏ʹݴͬͯ

    View Slide

  137. ʮ͋ɺ҉߸Խͯ͠όϥͯ͠ੈքதʹ෼ࢄͤ͞Αʯ
    ʠͱ΍͔͘ʡ͞Εͨ͘ͳ͍ਓɿ
    ڕΛӅ͢ͳΒւͩΑͶ
    εϚϗͷւ΁

    View Slide

  138. ݤ"
    ݤ#
    ݤ$
    ݤ%
    ҉߸Խͨ͠ϑΝΠϧΛ෼ׂͯ͠഑෍ɺ഑෍ઌͷݤͰߋʹ҉߸Խ
    ΋͏ͪΐͬͱ۩ମతʹ͸

    View Slide

  139. ·ͨ͸
    ݤ" ݤ# ݤ$ ݤ%
    ద౰ͳ਺ϗοϓͤͯ͞ɺ҉߸Խͱෳ߹Λ܁Γସ͑͢
    ࠷ऴతʹ %ʹ࣋ͬͯͯ΋Β͏

    View Slide

  140. https://fcm.googleapis.com/v1/projects/{project-id}/messages:send
    headers: {
    'content-type': 'application/json',
    authorization: `Bearer ${VALID_OAUTH_2_0_TOKEN}`
    },
    body: JSON.stringify({
    message: {
    token,
    data: {
    ...
    }
    }
    })
    1VTI"1*ͷ࿩ʹཱͪฦΔͱ

    View Slide

  141. https://fcm.googleapis.com/v1/projects/{project-id}/messages:send
    headers: {
    'content-type': 'application/json',
    authorization: `Bearer ${VALID_OAUTH_2_0_TOKEN}`
    },
    body: JSON.stringify({
    message: {
    token,
    data: {
    ...
    }
    }
    })
    1VTI"1*ͷ࿩ʹཱͪฦΔͱ
    ୭ʹૹΔ͔
    αʔόʔͷೝূΩʔ
    ͜ͷ͕̎ͭ͋Ε͹ 8FC1VTI͸
    Ͳ͔͜ΒͰ΋୭͔ΒͰ΋ૹΕΔ

    View Slide

  142. ฦͯ͠΄͍࣌͠ʢෳ߹࣌ʣ
    ฦͯ͠ʂ ͍ͬͯ͏ 1VTI௨஌ΛૹΔ
    ෳ߹
    ෳ߹
    ෳ߹
    ෳ߹
    ݟΕΔʂ
    ݤ"
    ݤ#
    ݤ$
    ݤ%

    View Slide

  143. ·ͨ͸
    ݤ" ݤ# ݤ$ ݤ%
    ฦͯ͠ʂ ฦͯͩͬͯ͠ ฦͯͩͬͯ͠ ͓ͬ

    View Slide

  144. ·ͨ͸
    ݤ" ݤ# ݤ$ ݤ%
    ฦͯ͠ʂ ฦͯͩͬͯ͠ ฦͯͩͬͯ͠ ͓ͬ
    ෳ߹
    ෳ߹
    ෳ߹
    ෳ߹
    ݟΕΔʂ

    View Slide







  145. Ԡ౴࣌ؒ
    ࣌ࠁ
    ฏۉԠ౴࣌ؒ
    ͦΕͬͯݱ࣮తʁ
    ௐࠪ݁Ռ 1VTI"1*ͬͯΈΜͳͲΕ͘Β͍Ͱฦͯ͘͠ΕΔͷʁ
    ஗͍

    View Slide

  146. ͦΕͬͯݱ࣮తʁ
    ௐࠪ݁Ռ 1VTI"1*ͬͯΈΜͳͲΕ͘Β͍Ͱฦͯ͘͠ΕΔͷʁ
    Ԡ౴଎౓ʢฏۉʣ
    Ұ൪ૣ͔ͬͨԠ౴ʢฏۉʣ
    Ұ൪஗͔ͬͨԠ౴ʢฏۉʣ
    Ԡ౴཰ʢฏۉʣ >

    View Slide

  147. 1VTIͰͲͷ͘Β͍ૹ৴Ͱ͖Δ
    ͬͪ͜ͷλΠϓͳΒ ,#ҎԼʹͳΔ·Ͱ෼ׂͨ͠Βཧ࿦্͸͍͚Δ

    View Slide

  148. 1VTIͰͲͷ͘Β͍ૹ৴Ͱ͖Δ
    ͬͪ͜ͷλΠϓ͸αʔόʔʹ͚͋ͣͯɺ
    ࡏΓॲΛڭ͑Δͱ͔͠ͳ͍ͱ͍͚ͳ͍
    ͜͜ʹૹͬͨΑ
    Cc2H5BfqPf+L/Z
    Cc2H5BfqPf+L/Z
    ͜͜ʹૹͬͨΑ
    Cc2H5BfqPf+L/Z
    ͜͜ʹૹͬͨΑ
    Cc2H5BfqPf+L/Z

    View Slide

  149. ·ͱΊ
    8FCͰͰ͖ΔମݧΛߟ͑Δձ

    View Slide

  150. ̏ͭͷδϟϯϧʹ෼͚ͯ 8FC"1*ͷ࢖͍ํΛ঺հ
    ૊Έ߹Θͤͯ࢖͏ͱ໘ന͍Αͱ͍͏࿩Λఴ͑ͯ
    8FC8PSLFS
    8FC"TTFNCMZ
    1VTI"1*
    8FC(- UISFFKT

    8FC"VEJP"1*
    ͳʹ͔໘ന͍͜ͱͷ͖͔͚ͬʹͳͬͯ͘ΕΔͱྑ͍Ͱ͢

    View Slide

  151. ંݪ ϨΦφϧυݡ
    [email protected]
    ϑϩϯτΤϯυΤϯδχΞɺͨ·ʹ69σβΠφʔ
    LINE 株式会社 UIT 室
    User Interface + Technology team @LINE corp

    View Slide

  152. ͋Γ͕ͱ͏͍͟͝·ͨ͠

    View Slide